use serde_json::json;
use helios_persistence::core::{ResourceStorage, TransactionProvider};
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())
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_commit() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({
"resourceType": "Patient",
"name": [{"family": "TransactionTest"}]
});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
backend.commit_transaction(tx).await.unwrap();
let read = backend
.read(&tenant, "Patient", created.id())
.await
.unwrap();
assert!(read.is_some());
assert_eq!(read.unwrap().content()["name"][0]["family"], "TransactionTest");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_multiple_operations() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient1 = json!({"resourceType": "Patient", "name": [{"family": "First"}]});
let patient2 = json!({"resourceType": "Patient", "name": [{"family": "Second"}]});
let patient3 = json!({"resourceType": "Patient", "name": [{"family": "Third"}]});
let p1 = backend
.create_in_transaction(&tx, "Patient", patient1)
.await
.unwrap();
let p2 = backend
.create_in_transaction(&tx, "Patient", patient2)
.await
.unwrap();
let p3 = backend
.create_in_transaction(&tx, "Patient", patient3)
.await
.unwrap();
backend.commit_transaction(tx).await.unwrap();
assert!(backend.exists(&tenant, "Patient", p1.id()).await.unwrap());
assert!(backend.exists(&tenant, "Patient", p2.id()).await.unwrap());
assert!(backend.exists(&tenant, "Patient", p3.id()).await.unwrap());
let count = backend.count(&tenant, Some("Patient")).await.unwrap();
assert_eq!(count, 3);
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_create_then_update() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({
"resourceType": "Patient",
"name": [{"family": "Original"}]
});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
let updated_content = json!({
"resourceType": "Patient",
"name": [{"family": "Updated"}]
});
backend
.update_in_transaction(&tx, &created, updated_content)
.await
.unwrap();
backend.commit_transaction(tx).await.unwrap();
let read = backend
.read(&tenant, "Patient", created.id())
.await
.unwrap()
.unwrap();
assert_eq!(read.content()["name"][0]["family"], "Updated");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_create_then_delete() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({"resourceType": "Patient"});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
backend
.delete_in_transaction(&tx, "Patient", created.id())
.await
.unwrap();
backend.commit_transaction(tx).await.unwrap();
assert!(!backend.exists(&tenant, "Patient", created.id()).await.unwrap());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_abort() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({
"resourceType": "Patient",
"name": [{"family": "ShouldNotExist"}]
});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
let created_id = created.id().to_string();
backend.abort_transaction(tx).await.unwrap();
let read = backend.read(&tenant, "Patient", &created_id).await.unwrap();
assert!(read.is_none());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_abort_multiple() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let existing = backend
.create(
&tenant,
"Patient",
json!({"resourceType": "Patient", "name": [{"family": "Existing"}]}),
)
.await
.unwrap();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let new_patient = json!({"resourceType": "Patient", "name": [{"family": "New"}]});
let new_created = backend
.create_in_transaction(&tx, "Patient", new_patient)
.await
.unwrap();
let new_id = new_created.id().to_string();
backend
.update_in_transaction(
&tx,
&existing,
json!({"resourceType": "Patient", "name": [{"family": "Modified"}]}),
)
.await
.unwrap();
backend.abort_transaction(tx).await.unwrap();
assert!(!backend.exists(&tenant, "Patient", &new_id).await.unwrap());
let read = backend
.read(&tenant, "Patient", existing.id())
.await
.unwrap()
.unwrap();
assert_eq!(read.content()["name"][0]["family"], "Existing");
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_isolation_uncommitted() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({"resourceType": "Patient"});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
let count_outside = backend.count(&tenant, Some("Patient")).await.unwrap();
backend.commit_transaction(tx).await.unwrap();
let count_after = backend.count(&tenant, Some("Patient")).await.unwrap();
assert_eq!(count_after, count_outside + 1);
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_tenant_isolation() {
let backend = create_sqlite_backend();
let tenant_a = TenantContext::new(TenantId::new("tenant-a"), TenantPermissions::full_access());
let tenant_b = TenantContext::new(TenantId::new("tenant-b"), TenantPermissions::full_access());
let tx_a = backend.begin_transaction(&tenant_a).await.unwrap();
let patient = json!({"resourceType": "Patient", "name": [{"family": "TenantA"}]});
let created = backend
.create_in_transaction(&tx_a, "Patient", patient)
.await
.unwrap();
backend.commit_transaction(tx_a).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_transaction_error_recovery() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({"resourceType": "Patient"});
let created = backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
let fake_resource = helios_persistence::types::StoredResource::new(
"Patient",
"non-existent-id",
tenant.tenant_id().clone(),
json!({"resourceType": "Patient"}),
);
let result = backend
.update_in_transaction(&tx, &fake_resource, json!({"resourceType": "Patient"}))
.await;
assert!(result.is_err());
backend.abort_transaction(tx).await.unwrap();
assert!(!backend.exists(&tenant, "Patient", created.id()).await.unwrap());
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_nested_transactions() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let outer_tx = backend.begin_transaction(&tenant).await.unwrap();
let patient = json!({"resourceType": "Patient", "name": [{"family": "Outer"}]});
backend
.create_in_transaction(&outer_tx, "Patient", patient)
.await
.unwrap();
let inner_result = backend.begin_transaction(&tenant).await;
if inner_result.is_err() {
backend.abort_transaction(outer_tx).await.unwrap();
let count = backend.count(&tenant, Some("Patient")).await.unwrap();
assert_eq!(count, 0);
}
}
#[cfg(feature = "sqlite")]
#[tokio::test]
async fn test_transaction_batch_operations() {
let backend = create_sqlite_backend();
let tenant = create_tenant();
let tx = backend.begin_transaction(&tenant).await.unwrap();
for i in 0..100 {
let patient = json!({
"resourceType": "Patient",
"name": [{"family": format!("Patient{}", i)}]
});
backend
.create_in_transaction(&tx, "Patient", patient)
.await
.unwrap();
}
backend.commit_transaction(tx).await.unwrap();
let count = backend.count(&tenant, Some("Patient")).await.unwrap();
assert_eq!(count, 100);
}