use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Layer;
use tracing_throttle::infrastructure::mocks::MockCaptureLayer;
use tracing_throttle::{Policy, TracingRateLimitLayer};
#[test]
fn test_all_fields_included_by_default() {
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, || {
for user_id in 1..=5 {
tracing::error!(user_id = user_id, "Failed to fetch user");
}
});
assert_eq!(
capture.count(),
5,
"Events with different field values should NOT be throttled together"
);
}
#[test]
fn test_same_field_values_are_throttled() {
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, || {
for _ in 0..5 {
tracing::error!(user_id = 123, "Failed to fetch user");
}
});
assert_eq!(
capture.count(),
2,
"Events with same field values should be throttled together"
);
}
#[test]
fn test_excluded_fields_not_in_signature() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_excluded_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, || {
for i in 0..5 {
tracing::error!(
user_id = 123,
request_id = format!("req-{}", i),
"Failed to fetch user"
);
}
});
assert_eq!(
capture.count(),
2,
"Excluded fields should not affect signature"
);
}
#[test]
fn test_multiple_excluded_fields() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_excluded_fields(vec!["request_id".to_string(), "trace_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, || {
for i in 0..5 {
tracing::error!(
user_id = 123,
request_id = format!("req-{}", i),
trace_id = format!("trace-{}", i),
"Failed to fetch user"
);
}
});
assert_eq!(
capture.count(),
2,
"Multiple excluded fields should all be excluded"
);
}
#[test]
fn test_different_error_codes_not_throttled() {
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, || {
for _ in 0..3 {
tracing::error!(error_code = "AUTH_FAILED", "Authentication failed");
}
for _ in 0..3 {
tracing::error!(error_code = "TIMEOUT", "Request timeout");
}
});
assert_eq!(
capture.count(),
4,
"Different field values create different signatures"
);
}
#[test]
fn test_no_fields_on_event() {
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, || {
for _ in 0..5 {
tracing::error!("Simple error without fields");
}
});
assert_eq!(
capture.count(),
2,
"Events without fields share the same signature"
);
}
#[test]
fn test_combined_span_context_and_event_fields() {
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 = tracing::info_span!("request", user_id = "alice");
let _enter = span.enter();
for _ in 0..3 {
tracing::error!(error_code = "AUTH_FAILED", "Authentication failed");
}
}
{
let span = tracing::info_span!("request", user_id = "alice");
let _enter = span.enter();
for _ in 0..3 {
tracing::error!(error_code = "TIMEOUT", "Request timeout");
}
}
{
let span = tracing::info_span!("request", user_id = "bob");
let _enter = span.enter();
for _ in 0..3 {
tracing::error!(error_code = "AUTH_FAILED", "Authentication failed");
}
}
});
assert_eq!(
capture.count(),
6,
"Span context and event fields both contribute to signature"
);
}
#[test]
fn test_excluded_fields_with_span_context() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_span_context_fields(vec!["user_id".to_string()])
.with_excluded_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 span = tracing::info_span!("request", user_id = "alice");
let _enter = span.enter();
for i in 0..5 {
tracing::error!(
error_code = "AUTH_FAILED",
request_id = format!("req-{}", i),
"Authentication failed"
);
}
});
assert_eq!(capture.count(), 2, "Excluded fields work with span context");
}
#[test]
fn test_excluded_field_not_present() {
let rate_limit = TracingRateLimitLayer::builder()
.with_policy(Policy::count_based(2).unwrap())
.with_excluded_fields(vec!["request_id".to_string(), "trace_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, || {
for _ in 0..5 {
tracing::error!(user_id = 123, "Failed to fetch user");
}
for _ in 0..5 {
tracing::error!(user_id = 456, "Failed to fetch user");
}
});
assert_eq!(
capture.count(),
4,
"Excluded fields that aren't present should not affect signature"
);
}