use rust_dix::*;
use rust_ef::db_context::DbContext;
use rust_ef::di::DbContextServiceCollectionExt as _;
use rust_ef::prelude::*;
use rust_ef_sqlite::DbContextOptionsBuilderExt as _;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, EntityType, Default, PartialEq)]
#[table("rw_items")]
struct RwItem {
#[primary_key]
#[auto_increment]
id: i32,
#[required]
name: String,
}
fn db_path(tag: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"rust_ef_rw_{}_{}_{}.db",
std::process::id(),
tag,
n
))
}
fn cleanup(path: &Path) {
for suffix in ["", "-wal", "-shm"] {
let file = PathBuf::from(format!("{}{}", path.display(), suffix));
let _ = std::fs::remove_file(file);
}
}
#[inject(scoped)]
struct ReadWriteHandler {
#[inject(owned, key = "write")]
write: DbContext,
#[inject(owned, key = "read")]
read: DbContext,
}
impl ReadWriteHandler {
async fn handle(&mut self) -> EFResult<(i64, i64)> {
self.write.ensure_created().await?;
self.write.add::<RwItem>(RwItem {
id: 0,
name: "created".into(),
});
self.write.save_changes().await?;
let from_write = self.write.set::<RwItem>().query().count().await?;
let from_read = self.read.set::<RwItem>().query().count().await?;
Ok((from_write, from_read))
}
}
#[tokio::test]
async fn keyed_read_write_contexts_use_separate_options_and_pools() {
let path = db_path("contexts");
let path_str = path.to_str().expect("utf-8 temp path").to_string();
let write_cs = path_str.clone();
let read_cs = path_str.clone();
let provider: std::sync::Arc<ServiceProvider> = ServiceCollection::from_injected()
.add_dbcontext_keyed("write", move |options| {
options.use_default_entity_model();
options.use_sqlite_with(&write_cs, |options| {
options.max_pool_size(1);
});
})
.add_dbcontext_keyed("read", move |options| {
options.use_default_entity_model();
options.use_sqlite(&read_cs);
})
.build()
.expect("build provider");
let mut handler: ReadWriteHandler = provider.get_owned().expect("read/write handler");
let (from_write, from_read) = handler.handle().await.expect("handler");
assert_eq!(from_write, 1, "write context must see its own write");
assert_eq!(from_read, 1, "read context must see the committed row");
cleanup(&path);
}
#[tokio::test]
async fn keyed_replica_without_shared_model_has_actionable_error() {
let path = db_path("missing_shared_model");
let cs = path.to_str().expect("utf-8 temp path").to_string();
let provider: std::sync::Arc<ServiceProvider> = ServiceCollection::new()
.add_dbcontext_keyed("read", move |options| {
options.use_sqlite(&cs);
})
.build()
.expect("build provider");
let ctx: DbContext = provider.get_keyed_owned("read").expect("read context");
let err = match ctx.ensure_created().await {
Ok(()) => panic!("keyed context without the shared model must fail"),
Err(err) => err,
};
assert!(
err.message().contains("use_default_entity_model"),
"error must point at the fix, got: {err}"
);
cleanup(&path);
}
#[test]
fn sqlite_pool_rejects_zero_and_memory() {
let err = match rust_ef_sqlite::SqliteProvider::new_with_pool(":memory:", 1) {
Ok(_) => panic!("in-memory pool must be rejected"),
Err(err) => err,
};
assert!(matches!(err, EFError::Configuration(..)));
let path = db_path("zero");
let err = match rust_ef_sqlite::SqliteProvider::new_with_pool(&path, 0) {
Ok(_) => panic!("zero-sized pool must be rejected"),
Err(err) => err,
};
assert!(matches!(err, EFError::Configuration(..)));
cleanup(&path);
}