use tracing::info_span;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Layer;
use tracing_throttle::infrastructure::mocks::MockCaptureLayer;
use tracing_throttle::{Policy, TracingRateLimitLayer};
#[test]
fn test_span_context_per_user_rate_limiting() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_span_context_fields(vec!["user_id".to_string()])
.build()
.unwrap();
let capture = MockCaptureLayer::new();
let rate_limit_filter = rate_limit.clone();
let subscriber = tracing_subscriber::registry()
.with(rate_limit)
.with(capture.clone().with_filter(rate_limit_filter));
tracing::subscriber::with_default(subscriber, || {
{
let span = info_span!("request", user_id = "alice");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
{
let span = info_span!("request", user_id = "bob");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
});
assert_eq!(
capture.count(),
4,
"Should rate limit independently per user_id"
);
}
#[test]
fn test_missing_span_context_field() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_span_context_fields(vec!["user_id".to_string()])
.build()
.unwrap();
let capture = MockCaptureLayer::new();
let rate_limit_filter = rate_limit.clone();
let subscriber = tracing_subscriber::registry()
.with(rate_limit)
.with(capture.clone().with_filter(rate_limit_filter));
tracing::subscriber::with_default(subscriber, || {
{
let span = info_span!("request", request_id = "req-123");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
{
let span = info_span!("request", request_id = "req-456");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
});
assert_eq!(
capture.count(),
4,
"Events in different loops have different signatures"
);
}
#[test]
fn test_nested_span_inheritance() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_span_context_fields(vec!["request_id".to_string()])
.build()
.unwrap();
let capture = MockCaptureLayer::new();
let rate_limit_filter = rate_limit.clone();
let subscriber = tracing_subscriber::registry()
.with(rate_limit)
.with(capture.clone().with_filter(rate_limit_filter));
tracing::subscriber::with_default(subscriber, || {
let request_span = info_span!("request", request_id = "req-123");
let _request_enter = request_span.enter();
tracing::info!("event 1");
{
let handler_span = info_span!("handler");
let _handler_enter = handler_span.enter();
tracing::info!("event 2"); tracing::info!("event 3"); }
tracing::info!("event 4"); });
assert_eq!(
capture.count(),
4,
"Each event location creates a unique signature even with shared context"
);
}
#[test]
fn test_multiple_context_fields() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_span_context_fields(vec!["user_id".to_string(), "tenant_id".to_string()])
.build()
.unwrap();
let capture = MockCaptureLayer::new();
let rate_limit_filter = rate_limit.clone();
let subscriber = tracing_subscriber::registry()
.with(rate_limit)
.with(capture.clone().with_filter(rate_limit_filter));
tracing::subscriber::with_default(subscriber, || {
{
let span = info_span!("request", user_id = "alice", tenant_id = "tenant1");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
{
let span = info_span!("request", user_id = "alice", tenant_id = "tenant2");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
{
let span = info_span!("request", user_id = "bob", tenant_id = "tenant1");
let _enter = span.enter();
for _ in 0..3 {
tracing::info!("event");
}
}
});
assert_eq!(
capture.count(),
6,
"Should rate limit per combination of all context fields"
);
}
#[test]
fn test_no_span_context_configured() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.build()
.unwrap();
let capture = MockCaptureLayer::new();
let rate_limit_filter = rate_limit.clone();
let subscriber = tracing_subscriber::registry()
.with(rate_limit)
.with(capture.clone().with_filter(rate_limit_filter));
tracing::subscriber::with_default(subscriber, || {
{
let span = info_span!("request", user_id = "alice");
let _enter = span.enter();
for _ in 0..2 {
tracing::info!("event");
}
}
{
let span = info_span!("request", user_id = "bob");
let _enter = span.enter();
for _ in 0..2 {
tracing::info!("event");
}
}
});
assert_eq!(
capture.count(),
4,
"Without span context, events are still distinguished by source location"
);
}