use openehr_sqlite::{SqliteDialect, SqliteStore};
use openehr_store::{Store, conformance, ddl_script};
#[test]
fn the_shared_suite_passes_against_a_real_database() {
let mut store = SqliteStore::in_memory().expect("open");
conformance::run(&mut store).expect("conformance suite");
}
#[test]
fn the_dialect_is_self_consistent() {
conformance::check_dialect(&SqliteDialect);
}
#[test]
fn the_database_itself_refuses_to_mutate_a_version() {
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("create ehr");
store
.create_contribution(
ehr.ehr_id(),
&conformance::sample_contribution("22222222-3333-4444-5555-666666666666", &[1]),
)
.expect("contribution");
store
.commit_composition(
ehr.ehr_id(),
&conformance::sample_version(1, None, 5),
"22222222-3333-4444-5555-666666666666",
)
.expect("commit");
let update = store.connection().execute(
"UPDATE openehr_version SET lifecycle_state_code = '523'",
[],
);
assert!(update.is_err(), "an UPDATE on openehr_version succeeded");
let delete = store
.connection()
.execute("DELETE FROM openehr_version", []);
assert!(delete.is_err(), "a DELETE on openehr_version succeeded");
let row = store
.latest_version(&"87284370-2D4B-4E3D-A3F3-F303D2F4F34B".parse().unwrap())
.expect("still readable");
assert_eq!(row.lifecycle_state_code, "532");
}
#[test]
fn an_invalid_composition_is_refused_before_it_is_written() {
use openehr::rm::common::{AuditDetails, LocatableAttrs, OriginalVersion, PartyIdentified};
use openehr::rm::data_types::{CodePhrase, DvDateTime};
use openehr::rm::ehr::Composition;
use openehr::terminology::{audit_change_type, composition_category, version_lifecycle_state};
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("create ehr");
store
.create_contribution(
ehr.ehr_id(),
&conformance::sample_contribution("22222222-3333-4444-5555-666666666666", &[1]),
)
.expect("contribution");
let rootless = Composition::new(
LocatableAttrs::named("Encounter", "openEHR-EHR-COMPOSITION.encounter.v1").unwrap(),
composition_category::EVENT,
PartyIdentified::named("Dr A Nurse").unwrap().into(),
CodePhrase::new("ISO_639-1", "en").unwrap(),
CodePhrase::new("ISO_3166-1", "GB").unwrap(),
)
.unwrap();
let owner = openehr::base::ObjectRef::new(
"local",
"EHR",
openehr::base::ObjectId::HierObjectId(ehr.ehr_id().clone()),
)
.unwrap();
let version = OriginalVersion::new(
format!("{}::{}::1", conformance::RECORD, conformance::SYSTEM)
.parse()
.unwrap(),
None,
version_lifecycle_state::COMPLETE,
Some(rootless),
AuditDetails::new(
conformance::SYSTEM,
DvDateTime::new("2026-08-01T09:05:00Z").unwrap(),
audit_change_type::CREATION,
PartyIdentified::named("Dr A Nurse").unwrap().into(),
)
.unwrap(),
owner,
)
.unwrap();
let result = store.commit_composition(
ehr.ehr_id(),
&version.into(),
"22222222-3333-4444-5555-666666666666",
);
assert!(
matches!(result, Err(openehr_store::StoreError::Invalid(_))),
"an invalid composition was accepted"
);
let count: i64 = store
.connection()
.query_row("SELECT COUNT(*) FROM openehr_versioned_object", [], |r| {
r.get(0)
})
.unwrap();
assert_eq!(count, 0, "a refused commit left a container behind");
}
#[test]
fn a_partial_or_offset_instant_survives_storage_verbatim() {
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("create");
let read = store.get_ehr(ehr.ehr_id()).expect("read");
assert_eq!(
read.time_created().as_str(),
ehr.time_created().as_str(),
"the authoritative lexical form was altered by a round trip"
);
}
#[test]
fn the_generated_ddl_executes_and_is_idempotent() {
let script = ddl_script(&SqliteDialect);
let connection = rusqlite::Connection::open_in_memory().expect("open");
connection.execute_batch(&script).expect("first install");
connection.execute_batch(&script).expect("second install");
}
#[test]
fn the_chain_links_versions_and_notices_a_rewrite() {
use openehr::security::{Chain, Digest256};
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("ehr");
store
.create_contribution(
ehr.ehr_id(),
&conformance::sample_contribution("c1", &[1, 2, 3]),
)
.expect("contribution");
for n in 1..=3u32 {
let preceding = (n > 1).then(|| n - 1);
store
.commit_composition(
ehr.ehr_id(),
&conformance::sample_version(n, preceding, n),
"c1",
)
.unwrap_or_else(|e| panic!("commit {n}: {e}"));
}
let container = ehr.ehr_id().clone();
let all = store.all_versions(&container).expect("all_versions");
assert_eq!(all.len(), 3);
assert_eq!(all[0].chain.previous, [0u8; 32], "first links to genesis");
for pair in all.windows(2) {
assert_eq!(
pair[1].chain.previous, pair[0].chain.digest,
"version {} does not link to {}",
pair[1].uid, pair[0].uid
);
}
let mut recomputed = Chain::new();
for row in &all {
let content: serde_json::Value = row
.data_json
.as_deref()
.map(|j| serde_json::from_str(j).expect("stored JSON parses"))
.expect("a committed version has content");
recomputed
.append(row.uid.clone(), &Some(content), None)
.expect("append");
}
assert_eq!(
recomputed.head(),
Digest256::from_bytes(all[2].chain.digest),
"the chain recomputed from stored rows must reach the stored head"
);
let connection = store.connection();
connection
.execute_batch("DROP TRIGGER trg_openehr_version_no_update;")
.expect("drop trigger");
connection
.execute(
"UPDATE openehr_version SET data_json = replace(data_json, 'Encounter 2', 'Tampered') \
WHERE trunk_version = 2",
[],
)
.expect("rewrite");
let after = store.all_versions(&container).expect("all_versions");
let mut recheck = Chain::new();
for row in &after {
let content: serde_json::Value =
serde_json::from_str(row.data_json.as_deref().expect("content")).expect("parses");
recheck
.append(row.uid.clone(), &Some(content), None)
.expect("append");
}
assert_ne!(
recheck.head(),
Digest256::from_bytes(after[2].chain.digest),
"a rewritten version must not recompute to the stored head — \
the chain would be evidence of nothing"
);
}
#[test]
fn a_database_from_another_schema_version_is_refused() {
use openehr_store::StoreError;
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("fresh install");
store.install().expect("re-install is a no-op");
store
.connection()
.execute("UPDATE openehr_schema_version SET version = 99", [])
.expect("rewrite version");
match store.install() {
Err(StoreError::SchemaVersionMismatch { found, expected }) => {
assert_eq!(found, 99);
assert_eq!(expected, openehr_store::SCHEMA_VERSION);
}
other => panic!("expected a version mismatch, got {other:?}"),
}
}
#[test]
fn a_database_predating_the_version_table_is_refused() {
use openehr_store::StoreError;
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("ehr");
store
.connection()
.execute("DELETE FROM openehr_schema_version", [])
.expect("clear version");
assert!(
matches!(
store.install(),
Err(StoreError::SchemaVersionMismatch { found: 0, .. })
),
"a populated database with no recorded version must be refused"
);
}
#[test]
fn a_truncated_chain_verifies_clean_and_only_the_checkpoint_notices() {
use openehr::security::{Chain, Digest256};
let mut store = SqliteStore::in_memory().expect("open");
store.install().expect("install");
let ehr = conformance::sample_ehr();
store.create_ehr(&ehr).expect("ehr");
store
.create_contribution(
ehr.ehr_id(),
&conformance::sample_contribution("c1", &[1, 2, 3]),
)
.expect("contribution");
for n in 1..=3u32 {
store
.commit_composition(
ehr.ehr_id(),
&conformance::sample_version(n, (n > 1).then(|| n - 1), n),
"c1",
)
.expect("commit");
}
let container = ehr.ehr_id().clone();
let before = store.chain_checkpoint(&container).expect("checkpoint");
assert!(before.starts_with("entries=3 "), "{before}");
assert!(
!before.contains("Encounter"),
"a checkpoint must carry no clinical content: {before}"
);
let connection = store.connection();
connection
.execute_batch("DROP TRIGGER trg_openehr_version_no_delete;")
.expect("drop trigger");
connection
.execute(
"DELETE FROM openehr_composition_index WHERE version_uid IN \
(SELECT uid FROM openehr_version WHERE trunk_version = 3)",
[],
)
.expect("remove index row");
connection
.execute("DELETE FROM openehr_version WHERE trunk_version = 3", [])
.expect("truncate");
let after = store.all_versions(&container).expect("all_versions");
assert_eq!(after.len(), 2);
let mut rebuilt = Chain::new();
for row in &after {
let content: serde_json::Value =
serde_json::from_str(row.data_json.as_deref().expect("content")).expect("parses");
rebuilt
.append(row.uid.clone(), &Some(content), None)
.expect("append");
}
assert_eq!(
rebuilt.head(),
Digest256::from_bytes(after[1].chain.digest),
"the truncated history verifies against itself — which is exactly the \
problem, and why a checkpoint is not optional"
);
let now = store.chain_checkpoint(&container).expect("checkpoint");
assert!(now.starts_with("entries=2 "), "{now}");
assert_ne!(
before, now,
"a checkpoint published before the truncation must not match after it"
);
}
#[test]
fn a_contribution_is_persisted_and_cannot_be_declared_twice() {
let mut store = SqliteStore::in_memory().expect("store");
store.install().expect("install");
let ehr = conformance::sample_ehr();
let ehr_id = ehr.ehr_id().clone();
store.create_ehr(&ehr).expect("ehr");
let uid = "7C2E4B90-1A3D-4E58-9F6B-0D8C7A5E4B31";
let contribution = conformance::sample_contribution(uid, &[1]);
store
.create_contribution(&ehr_id, &contribution)
.expect("first declaration");
let count: i64 = store
.connection()
.query_row(
"SELECT count(*) FROM openehr_contribution WHERE uid = ?1",
[uid],
|row| row.get(0),
)
.expect("query");
assert_eq!(count, 1, "the contribution was not written");
assert!(
matches!(
store.create_contribution(&ehr_id, &contribution),
Err(openehr_store::StoreError::Conflict { .. })
),
"a duplicate contribution was accepted"
);
}
#[test]
fn an_empty_database_predating_the_version_table_still_installs() {
let mut store = SqliteStore::in_memory().expect("store");
store.install().expect("install");
store
.connection()
.execute("DELETE FROM openehr_schema_version", [])
.expect("delete");
store
.install()
.expect("an empty database has nothing to lose and is treated as fresh");
store
.connection()
.execute("DELETE FROM openehr_schema_version", [])
.expect("delete");
store.create_ehr(&conformance::sample_ehr()).expect("ehr");
assert!(
matches!(
store.install(),
Err(openehr_store::StoreError::SchemaVersionMismatch { found: 0, .. })
),
"a populated database with no version was treated as fresh"
);
}
#[test]
fn a_checkpoint_carries_a_real_digest_and_the_store_names_itself() {
let mut store = SqliteStore::in_memory().expect("store");
store.install().expect("install");
assert_eq!(store.engine(), "SQLite");
let ehr = conformance::sample_ehr();
let ehr_id = ehr.ehr_id().clone();
store.create_ehr(&ehr).expect("ehr");
let contribution = "22222222-3333-4444-5555-666666666666";
store
.create_contribution(
&ehr_id,
&conformance::sample_contribution(contribution, &[1]),
)
.expect("contribution");
store
.commit_composition(
&ehr_id,
&conformance::sample_version(1, None, 5),
contribution,
)
.expect("commit");
let container = openehr::base::HierObjectId::from_uid_str(conformance::RECORD).expect("uid");
let checkpoint = store.chain_checkpoint(&container).expect("checkpoint");
let head = store.latest_version(&container).expect("head");
let expected = head.chain.digest.iter().fold(String::new(), |mut acc, b| {
use std::fmt::Write as _;
let _ = write!(acc, "{b:02x}");
acc
});
assert_eq!(expected.len(), 64);
assert!(
checkpoint.contains(&expected),
"the checkpoint carries no digest: {checkpoint}"
);
assert_ne!(expected, "0".repeat(64), "that is the genesis digest");
}