use serde_json::json;
use helios_persistence::core::{ResourceStorage, SearchProvider};
use helios_persistence::tenant::{TenantContext, TenantId, TenantPermissions};
use helios_persistence::types::{Pagination, SearchQuery};
#[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(id: &str) -> TenantContext {
TenantContext::new(TenantId::new(id), TenantPermissions::full_access())
}
fn create_patient_json(name: &str) -> serde_json::Value {
json!({
"resourceType": "Patient",
"name": [{"family": name}]
})
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_create_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let patient = create_patient_json("TenantA Patient");
let created = backend.create(&tenant_a, "Patient", patient).await.unwrap();
let read_a = backend
.read(&tenant_a, "Patient", created.id())
.await
.unwrap();
assert!(read_a.is_some());
let read_b = backend
.read(&tenant_b, "Patient", created.id())
.await
.unwrap();
assert!(read_b.is_none());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_exists_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let patient = create_patient_json("Test");
let created = backend.create(&tenant_a, "Patient", patient).await.unwrap();
assert!(backend.exists(&tenant_a, "Patient", created.id()).await.unwrap());
assert!(!backend.exists(&tenant_b, "Patient", created.id()).await.unwrap());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_read_batch_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let p1 = backend
.create(&tenant_a, "Patient", create_patient_json("A1"))
.await
.unwrap();
let p2 = backend
.create(&tenant_a, "Patient", create_patient_json("A2"))
.await
.unwrap();
backend
.create_or_update(&tenant_b, "Patient", "b-patient", create_patient_json("B1"))
.await
.unwrap();
let ids = vec![p1.id(), p2.id(), "b-patient"];
let batch_a = backend
.read_batch(&tenant_a, "Patient", &ids)
.await
.unwrap();
assert_eq!(batch_a.len(), 2);
for resource in &batch_a {
assert_eq!(resource.tenant_id().as_str(), "tenant-a");
}
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_count_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
for i in 0..5 {
backend
.create(&tenant_a, "Patient", create_patient_json(&format!("A{}", i)))
.await
.unwrap();
}
for i in 0..3 {
backend
.create(&tenant_b, "Patient", create_patient_json(&format!("B{}", i)))
.await
.unwrap();
}
let count_a = backend.count(&tenant_a, Some("Patient")).await.unwrap();
let count_b = backend.count(&tenant_b, Some("Patient")).await.unwrap();
assert_eq!(count_a, 5);
assert_eq!(count_b, 3);
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_search_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
for i in 0..3 {
backend
.create(&tenant_a, "Patient", create_patient_json("Smith"))
.await
.unwrap();
}
for i in 0..2 {
backend
.create(&tenant_b, "Patient", create_patient_json("Smith"))
.await
.unwrap();
}
let query = SearchQuery::new("Patient");
let result_a = backend
.search(&tenant_a, &query, Pagination::new(100))
.await
.unwrap();
let result_b = backend
.search(&tenant_b, &query, Pagination::new(100))
.await
.unwrap();
assert_eq!(result_a.resources.len(), 3);
for resource in &result_a.resources {
assert_eq!(resource.tenant_id().as_str(), "tenant-a");
}
assert_eq!(result_b.resources.len(), 2);
for resource in &result_b.resources {
assert_eq!(resource.tenant_id().as_str(), "tenant-b");
}
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_update_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let patient = create_patient_json("Original");
let created = backend.create(&tenant_a, "Patient", patient).await.unwrap();
let fake_resource = helios_persistence::types::StoredResource::new(
"Patient",
created.id(),
TenantId::new("tenant-b"),
json!({"resourceType": "Patient"}),
);
let result = backend
.update(&tenant_b, &fake_resource, json!({"resourceType": "Patient", "name": [{"family": "Hacked"}]}))
.await;
assert!(result.is_err());
let original = backend
.read(&tenant_a, "Patient", created.id())
.await
.unwrap()
.unwrap();
assert_eq!(original.content()["name"][0]["family"], "Original");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_delete_isolation() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let patient = create_patient_json("TenantA");
let created = backend.create(&tenant_a, "Patient", patient).await.unwrap();
let result = backend.delete(&tenant_b, "Patient", created.id()).await;
assert!(result.is_err());
assert!(backend.exists(&tenant_a, "Patient", created.id()).await.unwrap());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_same_id_different_tenants() {
let backend = create_sqlite_backend();
let tenant_a = create_tenant("tenant-a");
let tenant_b = create_tenant("tenant-b");
let patient_a = json!({
"resourceType": "Patient",
"name": [{"family": "TenantA Patient"}]
});
let patient_b = json!({
"resourceType": "Patient",
"name": [{"family": "TenantB Patient"}]
});
backend
.create_or_update(&tenant_a, "Patient", "shared-id", patient_a)
.await
.unwrap();
backend
.create_or_update(&tenant_b, "Patient", "shared-id", patient_b)
.await
.unwrap();
let read_a = backend
.read(&tenant_a, "Patient", "shared-id")
.await
.unwrap()
.unwrap();
let read_b = backend
.read(&tenant_b, "Patient", "shared-id")
.await
.unwrap()
.unwrap();
assert_eq!(read_a.content()["name"][0]["family"], "TenantA Patient");
assert_eq!(read_b.content()["name"][0]["family"], "TenantB Patient");
assert_ne!(read_a.tenant_id(), read_b.tenant_id());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_system_tenant_access() {
let backend = create_sqlite_backend();
let system = TenantContext::system();
let tenant_a = create_tenant("tenant-a");
let value_set = json!({
"resourceType": "ValueSet",
"name": "SharedValueSet"
});
let created = backend
.create(&system, "ValueSet", value_set)
.await
.unwrap();
let read_system = backend
.read(&system, "ValueSet", created.id())
.await
.unwrap();
assert!(read_system.is_some());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_cannot_modify_system_resources() {
let backend = create_sqlite_backend();
let system = TenantContext::system();
let tenant_a = create_tenant("tenant-a");
let value_set = json!({
"resourceType": "ValueSet",
"name": "SystemValueSet"
});
let created = backend
.create(&system, "ValueSet", value_set)
.await
.unwrap();
let result = backend.delete(&tenant_a, "ValueSet", created.id()).await;
assert!(result.is_err());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_hierarchical_tenant_access() {
let backend = create_sqlite_backend();
let parent = TenantContext::new(
TenantId::new("parent"),
TenantPermissions::builder()
.can_access_child_tenants(true)
.build(),
);
let child = create_tenant("parent/child");
let patient = create_patient_json("ChildPatient");
let created = backend.create(&child, "Patient", patient).await.unwrap();
let read_parent = backend
.read(&parent, "Patient", created.id())
.await;
}