mod common;
use rust_dix::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Debug, PartialEq)]
struct DbPool {
conn: String,
}
#[derive(Debug, PartialEq)]
struct Cache {
backend: String,
}
#[tokio::test]
async fn async_singleton_basic() {
let provider = ServiceCollection::new()
.async_singleton(|_| {
Box::pin(async {
Arc::new(DbPool {
conn: "pg://host".into(),
})
})
})
.build_async()
.await
.unwrap();
let pool: Arc<DbPool> = provider.get_async().await.unwrap();
assert_eq!(pool.conn, "pg://host");
}
#[tokio::test]
async fn async_singleton_caches() {
static CNT: AtomicUsize = AtomicUsize::new(0);
let provider = ServiceCollection::new()
.async_singleton(|_| {
CNT.fetch_add(1, Ordering::SeqCst);
Box::pin(async {
Arc::new(DbPool {
conn: "cached".into(),
})
})
})
.build_async()
.await
.unwrap();
let _a = provider.get_async::<DbPool>().await.unwrap();
let _b = provider.get_async::<DbPool>().await.unwrap();
assert_eq!(CNT.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn async_transient_new_each_time() {
static CNT: AtomicUsize = AtomicUsize::new(0);
let provider = ServiceCollection::new()
.async_transient(|_| {
CNT.fetch_add(1, Ordering::SeqCst);
Box::pin(async {
Arc::new(Cache {
backend: "redis".into(),
})
})
})
.build_async()
.await
.unwrap();
let _a = provider.get_async::<Cache>().await.unwrap();
let _b = provider.get_async::<Cache>().await.unwrap();
assert_eq!(CNT.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn async_keyed_singleton() {
let provider = ServiceCollection::new()
.async_keyed_singleton("a", |_| {
Box::pin(async {
Arc::new(DbPool {
conn: "db-a".into(),
})
})
})
.async_keyed_singleton("b", |_| {
Box::pin(async {
Arc::new(DbPool {
conn: "db-b".into(),
})
})
})
.build_async()
.await
.unwrap();
let a: Arc<DbPool> = provider.get_keyed_async("a").await.unwrap();
let b: Arc<DbPool> = provider.get_keyed_async("b").await.unwrap();
assert_eq!(a.conn, "db-a");
assert_eq!(b.conn, "db-b");
}
#[tokio::test]
async fn async_keyed_transient() {
static CNT: AtomicUsize = AtomicUsize::new(0);
let provider = ServiceCollection::new()
.async_keyed_transient("t", |_| {
CNT.fetch_add(1, Ordering::SeqCst);
Box::pin(async {
Arc::new(Cache {
backend: "mem".into(),
})
})
})
.build_async()
.await
.unwrap();
let _a = provider.get_keyed_async::<Cache>("t").await.unwrap();
let _b = provider.get_keyed_async::<Cache>("t").await.unwrap();
assert_eq!(CNT.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn async_mixed_sync_and_async() {
let provider = ServiceCollection::new()
.singleton(|_| {
Arc::new(common::Logger {
prefix: "sync".into(),
})
})
.async_singleton(|_| {
Box::pin(async {
Arc::new(DbPool {
conn: "mixed".into(),
})
})
})
.build_async()
.await
.unwrap();
let logger: Arc<common::Logger> = provider.get().unwrap();
assert_eq!(logger.prefix, "sync");
let pool: Arc<DbPool> = provider.get_async().await.unwrap();
assert_eq!(pool.conn, "mixed");
}
#[tokio::test]
async fn async_singleton_with_sync_dependency() {
let provider = ServiceCollection::new()
.singleton(|_| {
Arc::new(common::Logger {
prefix: "dep".into(),
})
})
.async_singleton(|p: Arc<ServiceProvider>| {
Box::pin(async move {
let logger: Arc<common::Logger> = p.get().unwrap();
Arc::new(DbPool {
conn: format!("pg://{}/db", logger.prefix),
})
})
})
.build_async()
.await
.unwrap();
let pool: Arc<DbPool> = provider.get_async().await.unwrap();
assert_eq!(pool.conn, "pg://dep/db");
}
#[tokio::test]
async fn async_service_rejected_on_sync_build() {
let result = ServiceCollection::new()
.async_singleton(|_| {
Box::pin(async {
Arc::new(DbPool {
conn: "nope".into(),
})
})
})
.build();
assert!(
matches!(result, Err(RdiError::AsyncServiceRequiresBuildAsync { .. })),
"build() must reject async registrations with a typed error"
);
}
#[tokio::test]
async fn async_with_owned_resolution() {
let provider = ServiceCollection::new()
.async_transient(|_| Box::pin(async { Arc::new(common::MyService { value: 99 }) }))
.build_async()
.await
.unwrap();
let svc: Arc<common::MyService> = provider.get_async().await.unwrap();
assert_eq!(svc.value, 99);
}
#[tokio::test]
async fn async_multiple_async_singletons() {
let provider = ServiceCollection::new()
.async_singleton(|_| {
Box::pin(async {
Arc::new(DbPool {
conn: "first".into(),
})
})
})
.async_singleton(|_| {
Box::pin(async {
Arc::new(Cache {
backend: "second".into(),
})
})
})
.build_async()
.await
.unwrap();
let pool: Arc<DbPool> = provider.get_async().await.unwrap();
let cache: Arc<Cache> = provider.get_async().await.unwrap();
assert_eq!(pool.conn, "first");
assert_eq!(cache.backend, "second");
}
#[tokio::test]
async fn async_instance_works_with_build_async() {
let pool = Arc::new(DbPool {
conn: "instance".into(),
});
let provider = ServiceCollection::new()
.instance(pool.clone())
.build_async()
.await
.unwrap();
let resolved: Arc<DbPool> = provider.get().unwrap();
assert!(Arc::ptr_eq(&pool, &resolved));
}
#[tokio::test]
async fn async_optional_missing() {
let provider = ServiceCollection::new().build_async().await.unwrap();
assert!(provider.get_optional::<DbPool>().is_none());
}
#[tokio::test]
async fn async_scoped_basic() {
let provider = ServiceCollection::new()
.async_scoped(|_| Box::pin(async { Arc::new(common::MyService { value: 42 }) }))
.build_async()
.await
.unwrap();
let scope = provider.scope();
let svc: Arc<common::MyService> = scope.get_async().await.unwrap();
assert_eq!(svc.value, 42);
}
#[tokio::test]
async fn async_scoped_keyed() {
let provider = ServiceCollection::new()
.async_keyed_scoped("s", |_| {
Box::pin(async { Arc::new(common::MyService { value: 7 }) })
})
.build_async()
.await
.unwrap();
let scope = provider.scope();
let svc: Arc<common::MyService> = scope.get_keyed_async("s").await.unwrap();
assert_eq!(svc.value, 7);
}
#[tokio::test]
async fn async_scoped_concurrent_factory_executes_once() {
use std::sync::atomic::{AtomicU64, Ordering};
static CALLS: AtomicU64 = AtomicU64::new(0);
let provider = ServiceCollection::new()
.async_scoped(|_| {
Box::pin(async {
CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(common::MyService { value: 99 })
})
})
.build_async()
.await
.unwrap();
let scope = provider.scope();
let s1 = scope.scope_provider().clone();
let s2 = scope.scope_provider().clone();
let (r1, r2) = tokio::join!(async { s1.get_async::<common::MyService>().await }, async {
s2.get_async::<common::MyService>().await
},);
let v1 = r1.unwrap();
let v2 = r2.unwrap();
assert_eq!(v1.value, 99);
assert_eq!(v2.value, 99);
assert_eq!(
CALLS.load(Ordering::SeqCst),
1,
"并发首次解析:async factory 应只执行一次"
);
}