use serde_json::json;
use helios_persistence::core::{ResourceStorage, VersionedStorage};
use helios_persistence::error::{ResourceError, StorageError};
use helios_persistence::tenant::{TenantContext, TenantId, TenantPermissions};
#[cfg(feature = "sqlite")]
use helios_persistence::backends::sqlite::SqliteBackend;
#[cfg(feature = "sqlite")]
fn create_sqlite_backend() -> SqliteBackend {
let backend = SqliteBackend::in_memory().expect("Failed to create SQLite backend");
backend.init_schema().expect("Failed to initialize schema");
backend
}
fn create_tenant() -> TenantContext {
TenantContext::new(TenantId::new("test-tenant"), TenantPermissions::full_access())
}
fn create_patient_json(name: &str) -> serde_json::Value {
json!({
"resourceType": "Patient",
"name": [{"family": name}],
"active": true
})
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_specific_version() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Version1");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let mut content2 = v1.content().clone();
content2["name"][0]["family"] = json!("Version2");
let v2 = backend.update(&tenant, &v1, content2).await.unwrap();
let mut content3 = v2.content().clone();
content3["name"][0]["family"] = json!("Version3");
let _v3 = backend.update(&tenant, &v2, content3).await.unwrap();
let read_v1 = backend
.vread(&tenant, "Patient", v1.id(), "1")
.await
.unwrap();
assert!(read_v1.is_some());
let read_v1 = read_v1.unwrap();
assert_eq!(read_v1.version_id(), "1");
assert_eq!(read_v1.content()["name"][0]["family"], "Version1");
let read_v2 = backend
.vread(&tenant, "Patient", v1.id(), "2")
.await
.unwrap();
assert!(read_v2.is_some());
let read_v2 = read_v2.unwrap();
assert_eq!(read_v2.version_id(), "2");
assert_eq!(read_v2.content()["name"][0]["family"], "Version2");
let read_v3 = backend
.vread(&tenant, "Patient", v1.id(), "3")
.await
.unwrap();
assert!(read_v3.is_some());
let read_v3 = read_v3.unwrap();
assert_eq!(read_v3.version_id(), "3");
assert_eq!(read_v3.content()["name"][0]["family"], "Version3");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_returns_correct_metadata() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Original");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let v1_time = v1.last_modified();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let v2 = backend
.update(&tenant, &v1, v1.content().clone())
.await
.unwrap();
let v2_time = v2.last_modified();
let read_v1 = backend
.vread(&tenant, "Patient", v1.id(), "1")
.await
.unwrap()
.unwrap();
assert_eq!(read_v1.etag(), "W/\"1\"");
assert_eq!(read_v1.last_modified(), v1_time);
let read_v2 = backend
.vread(&tenant, "Patient", v1.id(), "2")
.await
.unwrap()
.unwrap();
assert_eq!(read_v2.etag(), "W/\"2\"");
assert_eq!(read_v2.last_modified(), v2_time);
assert_eq!(read_v1.created_at(), read_v2.created_at());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_nonexistent_resource() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let result = backend
.vread(&tenant, "Patient", "nonexistent", "1")
.await
.unwrap();
assert!(result.is_none());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_nonexistent_version() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Smith");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let result = backend
.vread(&tenant, "Patient", v1.id(), "99")
.await
.unwrap();
assert!(result.is_none());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_invalid_version_format() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Smith");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let result = backend
.vread(&tenant, "Patient", v1.id(), "invalid")
.await;
match result {
Ok(None) => {}
Ok(Some(_)) => panic!("Should not find resource with invalid version"),
Err(_) => {} }
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_tenant_isolation() {
let backend = create_sqlite_backend();
let tenant1 = TenantContext::new(TenantId::new("tenant-1"), TenantPermissions::full_access());
let tenant2 = TenantContext::new(TenantId::new("tenant-2"), TenantPermissions::full_access());
let patient = create_patient_json("Smith");
let v1 = backend.create(&tenant1, "Patient", patient).await.unwrap();
let result = backend
.vread(&tenant2, "Patient", v1.id(), "1")
.await
.unwrap();
assert!(result.is_none(), "Should not see other tenant's resource");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_after_delete() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Smith");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let v2 = backend
.update(&tenant, &v1, v1.content().clone())
.await
.unwrap();
backend.delete(&tenant, "Patient", v1.id()).await.unwrap();
let read_v1 = backend
.vread(&tenant, "Patient", v1.id(), "1")
.await
.unwrap();
let read_v2 = backend
.vread(&tenant, "Patient", v1.id(), "2")
.await
.unwrap();
if read_v1.is_some() {
assert_eq!(read_v1.unwrap().version_id(), "1");
}
if read_v2.is_some() {
assert_eq!(read_v2.unwrap().version_id(), "2");
}
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_deleted_version() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Smith");
let v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
backend.delete(&tenant, "Patient", v1.id()).await.unwrap();
let deleted_version = backend
.vread(&tenant, "Patient", v1.id(), "2")
.await
.unwrap();
if let Some(deleted) = deleted_version {
assert!(deleted.is_deleted());
assert_eq!(deleted.version_id(), "2");
}
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_many_versions() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Version0");
let mut current = backend.create(&tenant, "Patient", patient).await.unwrap();
let id = current.id().to_string();
for i in 1..20 {
let mut content = current.content().clone();
content["name"][0]["family"] = json!(format!("Version{}", i));
current = backend.update(&tenant, ¤t, content).await.unwrap();
}
for version in 1..=20 {
let read = backend
.vread(&tenant, "Patient", &id, &version.to_string())
.await
.unwrap();
assert!(read.is_some(), "Version {} should exist", version);
let read = read.unwrap();
assert_eq!(read.version_id(), version.to_string());
assert_eq!(
read.content()["name"][0]["family"],
format!("Version{}", version - 1)
);
}
let read = backend
.vread(&tenant, "Patient", &id, "21")
.await
.unwrap();
assert!(read.is_none());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_vread_different_resource_types() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let patient = create_patient_json("Smith");
let patient_v1 = backend.create(&tenant, "Patient", patient).await.unwrap();
let patient_v2 = backend
.update(&tenant, &patient_v1, patient_v1.content().clone())
.await
.unwrap();
let obs = json!({
"resourceType": "Observation",
"status": "preliminary",
"code": {"coding": [{"code": "test"}]}
});
let obs_v1 = backend
.create(&tenant, "Observation", obs)
.await
.unwrap();
let mut obs_content = obs_v1.content().clone();
obs_content["status"] = json!("final");
let obs_v2 = backend
.update(&tenant, &obs_v1, obs_content)
.await
.unwrap();
let p_read_v1 = backend
.vread(&tenant, "Patient", patient_v1.id(), "1")
.await
.unwrap()
.unwrap();
let p_read_v2 = backend
.vread(&tenant, "Patient", patient_v1.id(), "2")
.await
.unwrap()
.unwrap();
assert_eq!(p_read_v1.resource_type(), "Patient");
assert_eq!(p_read_v2.resource_type(), "Patient");
let o_read_v1 = backend
.vread(&tenant, "Observation", obs_v1.id(), "1")
.await
.unwrap()
.unwrap();
let o_read_v2 = backend
.vread(&tenant, "Observation", obs_v1.id(), "2")
.await
.unwrap()
.unwrap();
assert_eq!(o_read_v1.resource_type(), "Observation");
assert_eq!(o_read_v1.content()["status"], "preliminary");
assert_eq!(o_read_v2.resource_type(), "Observation");
assert_eq!(o_read_v2.content()["status"], "final");
}