use cruxi::{Context, ContextBuilder};
use std::f64::consts::PI;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
#[test]
fn extensions_replace_value_of_same_type() {
let mut ctx = Context::new();
ctx.extensions_mut().insert(42u32);
assert_eq!(ctx.get::<u32>(), Some(&42));
ctx.extensions_mut().insert(100u32);
assert_eq!(ctx.get::<u32>(), Some(&100));
}
#[test]
fn extensions_multiple_types() {
let ctx = Context::new()
.with_value(42u32)
.with_value("hello".to_string())
.with_value(PI)
.with_value(vec![1i32, 2, 3]);
assert_eq!(ctx.get::<u32>(), Some(&42));
assert_eq!(ctx.get::<String>(), Some(&"hello".to_string()));
assert_eq!(ctx.get::<f64>(), Some(&PI));
assert_eq!(ctx.get::<Vec<i32>>(), Some(&vec![1, 2, 3]));
}
#[test]
fn extensions_remove_and_check() {
let mut ctx = Context::new();
ctx.extensions_mut().insert(42u32);
assert!(ctx.extensions().contains::<u32>());
let removed = ctx.extensions_mut().remove::<u32>();
assert!(removed.is_some());
assert!(!ctx.extensions().contains::<u32>());
assert_eq!(ctx.get::<u32>(), None);
}
#[test]
fn extensions_remove_nonexistent() {
let mut ctx = Context::new();
let removed = ctx.extensions_mut().remove::<u32>();
assert!(removed.is_none());
}
#[test]
fn context_clone_preserves_values() {
let ctx1 = Context::new()
.with_value(42u32)
.with_value("original".to_string());
let ctx2 = ctx1.clone();
assert_eq!(ctx2.get::<u32>(), Some(&42));
assert_eq!(ctx2.get::<String>(), Some(&"original".to_string()));
}
#[test]
fn context_clone_independent_modifications() {
let ctx1 = Context::new().with_value(42u32);
let mut ctx2 = ctx1.clone();
ctx2.extensions_mut().insert(100u32);
assert_eq!(ctx1.get::<u32>(), Some(&42));
assert_eq!(ctx2.get::<u32>(), Some(&100));
}
#[test]
fn context_shared_across_threads() {
let ctx = Arc::new(
Context::new()
.with_value(42u32)
.with_value("shared".to_string()),
);
let ctx_clone = Arc::clone(&ctx);
let handle = thread::spawn(move || {
assert_eq!(ctx_clone.get::<u32>(), Some(&42));
assert_eq!(ctx_clone.get::<String>(), Some(&"shared".to_string()));
});
assert_eq!(ctx.get::<u32>(), Some(&42));
handle.join().expect("Thread should complete");
}
#[test]
fn context_deadline_exactly_now() {
let now = Instant::now();
let ctx = Context::with_deadline(now);
std::thread::sleep(Duration::from_millis(1));
assert!(ctx.is_done());
}
#[test]
fn context_very_short_timeout() {
let ctx = Context::with_timeout(Duration::from_nanos(1));
std::thread::sleep(Duration::from_millis(1));
assert!(ctx.is_done());
assert!(ctx.time_remaining().is_none());
}
#[test]
fn context_very_long_timeout() {
let ctx = Context::with_timeout(Duration::from_secs(3600));
assert!(!ctx.is_done());
let remaining = ctx.time_remaining();
assert!(remaining.is_some());
assert!(remaining.unwrap() > Duration::from_secs(3500));
}
#[test]
fn context_time_remaining_decreases() {
let ctx = Context::with_timeout(Duration::from_millis(500));
let remaining1 = ctx.time_remaining().unwrap();
std::thread::sleep(Duration::from_millis(100));
let remaining2 = ctx.time_remaining().unwrap();
assert!(remaining2 < remaining1);
}
#[test]
fn context_no_deadline_never_expires() {
let ctx = Context::new();
assert!(ctx.deadline().is_none());
assert!(ctx.time_remaining().is_none());
assert!(!ctx.is_done());
}
#[test]
fn context_cancel_makes_done() {
let mut ctx = Context::new();
assert!(!ctx.is_done());
ctx.cancel();
assert!(ctx.is_done());
}
#[test]
fn context_cancel_with_deadline_still_done() {
let mut ctx = Context::with_timeout(Duration::from_secs(60));
assert!(!ctx.is_done());
ctx.cancel();
assert!(ctx.is_done());
}
#[test]
fn context_cancel_twice() {
let mut ctx = Context::new();
ctx.cancel();
ctx.cancel();
assert!(ctx.is_done());
}
#[test]
fn context_shorter_deadline_works() {
let far = Instant::now() + Duration::from_secs(60);
let near = Instant::now() + Duration::from_secs(10);
let ctx = Context::with_deadline(far);
let child = ctx.with_shorter_deadline(near);
assert_eq!(child.deadline(), Some(near));
}
#[test]
fn context_longer_deadline_keeps_original() {
let near = Instant::now() + Duration::from_secs(10);
let far = Instant::now() + Duration::from_secs(60);
let ctx = Context::with_deadline(near);
let child = ctx.with_shorter_deadline(far);
assert_eq!(child.deadline(), Some(near));
}
#[test]
fn context_shorter_timeout_on_no_deadline() {
let ctx = Context::new();
let child = ctx.with_shorter_timeout(Duration::from_secs(30));
assert!(child.deadline().is_some());
assert!(!child.is_done());
}
#[test]
fn context_shorter_deadline_preserves_values() {
let ctx = Context::new()
.with_value(42u32)
.with_value("preserved".to_string());
let child = ctx.with_shorter_timeout(Duration::from_secs(30));
assert_eq!(child.get::<u32>(), Some(&42));
assert_eq!(child.get::<String>(), Some(&"preserved".to_string()));
}
#[derive(Debug)]
struct HttpRequest {
method: String,
path: String,
}
#[derive(Debug)]
#[allow(dead_code)]
struct GrpcRequest {
service: String,
method: String,
}
#[test]
fn context_transport_wrong_type() {
use cruxi::TransportAs;
let ctx = Context::new().with_transport(HttpRequest {
method: "GET".to_string(),
path: "/api/users".to_string(),
});
let grpc = ctx.transport_as::<GrpcRequest>();
assert!(grpc.is_none());
}
#[test]
fn context_transport_no_transport_set() {
use cruxi::TransportAs;
let ctx = Context::new();
assert!(!ctx.has_transport());
let http = ctx.transport_as::<HttpRequest>();
assert!(http.is_none());
}
#[test]
fn context_transport_replace() {
use cruxi::TransportAs;
let ctx = Context::new()
.with_transport(HttpRequest {
method: "GET".to_string(),
path: "/v1".to_string(),
})
.with_transport(HttpRequest {
method: "POST".to_string(),
path: "/v2".to_string(),
});
let http = ctx.transport_as::<HttpRequest>();
assert!(http.is_some());
assert_eq!(http.unwrap().method, "POST");
assert_eq!(http.unwrap().path, "/v2");
}
#[test]
fn context_builder_empty() {
let ctx = ContextBuilder::new().build();
assert!(ctx.deadline().is_none());
assert!(!ctx.has_transport());
assert!(!ctx.is_done());
}
#[test]
fn context_builder_full_configuration() {
let ctx = ContextBuilder::new()
.with_timeout(Duration::from_secs(30))
.with_value(42u32)
.with_value("request-id-123".to_string())
.with_transport(HttpRequest {
method: "GET".to_string(),
path: "/api".to_string(),
})
.build();
assert!(ctx.deadline().is_some());
assert_eq!(ctx.get::<u32>(), Some(&42));
assert_eq!(ctx.get::<String>(), Some(&"request-id-123".to_string()));
assert!(ctx.has_transport());
}
#[test]
fn context_builder_deadline_precedence() {
let deadline = Instant::now() + Duration::from_secs(10);
let ctx = ContextBuilder::new()
.with_timeout(Duration::from_secs(60)) .with_deadline(deadline) .build();
assert_eq!(ctx.deadline(), Some(deadline));
}
#[test]
fn context_debug_format() {
let ctx = Context::new().with_value(42u32);
let debug_str = format!("{:?}", ctx);
assert!(debug_str.contains("Context"));
assert!(debug_str.contains("deadline"));
assert!(debug_str.contains("extensions"));
}
#[test]
fn extensions_debug_shows_count() {
let ctx = Context::new()
.with_value(1u32)
.with_value("test".to_string());
let debug_str = format!("{:?}", ctx.extensions());
assert!(debug_str.contains("count"));
assert!(debug_str.contains("2"));
}