#![allow(clippy::unwrap_used, clippy::print_stdout, clippy::print_stderr)]
#![allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] #![allow(clippy::format_push_string)] use std::{
collections::HashMap,
sync::{Arc, Mutex},
time::Instant,
};
use serde_json::json;
#[derive(Debug, Clone)]
struct TestTraceContext {
trace_id: String,
parent_span_id: String,
spans: Arc<Mutex<Vec<TestSpan>>>,
}
#[derive(Debug, Clone)]
struct TestSpan {
name: String,
span_id: String,
_parent_span_id: String,
trace_id: String,
start_time: Instant,
duration_us: u64,
attributes: HashMap<String, String>,
}
#[derive(Debug, Clone, Default)]
struct TestMetricsCollector {
entity_resolutions_total: Arc<Mutex<u64>>,
entity_resolutions_errors: Arc<Mutex<u64>>,
entity_resolution_durations: Arc<Mutex<Vec<u64>>>,
subgraph_requests_total: Arc<Mutex<u64>>,
subgraph_requests_errors: Arc<Mutex<u64>>,
subgraph_request_durations: Arc<Mutex<Vec<u64>>>,
mutations_total: Arc<Mutex<u64>>,
mutations_errors: Arc<Mutex<u64>>,
cache_hits: Arc<Mutex<u64>>,
cache_misses: Arc<Mutex<u64>>,
}
impl TestMetricsCollector {
fn new() -> Self {
Self::default()
}
fn record_entity_resolution(&self, duration_us: u64) {
*self.entity_resolutions_total.lock().unwrap() += 1;
self.entity_resolution_durations.lock().unwrap().push(duration_us);
}
fn _record_entity_resolution_error(&self) {
*self.entity_resolutions_errors.lock().unwrap() += 1;
}
fn record_subgraph_request(&self, duration_us: u64) {
*self.subgraph_requests_total.lock().unwrap() += 1;
self.subgraph_request_durations.lock().unwrap().push(duration_us);
}
fn _record_subgraph_request_error(&self) {
*self.subgraph_requests_errors.lock().unwrap() += 1;
}
fn record_mutation(&self, _duration_us: u64) {
*self.mutations_total.lock().unwrap() += 1;
}
fn _record_mutation_error(&self) {
*self.mutations_errors.lock().unwrap() += 1;
}
fn record_cache_hit(&self) {
*self.cache_hits.lock().unwrap() += 1;
}
fn record_cache_miss(&self) {
*self.cache_misses.lock().unwrap() += 1;
}
fn get_metrics_json(&self) -> serde_json::Value {
json!({
"entity_resolutions_total": *self.entity_resolutions_total.lock().unwrap(),
"entity_resolutions_errors": *self.entity_resolutions_errors.lock().unwrap(),
"entity_resolution_count": self.entity_resolution_durations.lock().unwrap().len(),
"subgraph_requests_total": *self.subgraph_requests_total.lock().unwrap(),
"subgraph_requests_errors": *self.subgraph_requests_errors.lock().unwrap(),
"subgraph_request_count": self.subgraph_request_durations.lock().unwrap().len(),
"mutations_total": *self.mutations_total.lock().unwrap(),
"mutations_errors": *self.mutations_errors.lock().unwrap(),
"cache_hits": *self.cache_hits.lock().unwrap(),
"cache_misses": *self.cache_misses.lock().unwrap(),
})
}
}
#[derive(Debug, Clone)]
struct TestLogEntry {
timestamp: Instant,
level: String,
message: String,
query_id: String,
trace_id: String,
context: serde_json::Value,
}
#[derive(Debug, Clone, Default)]
struct TestLogCollector {
logs: Arc<Mutex<Vec<TestLogEntry>>>,
}
impl TestLogCollector {
fn new() -> Self {
Self::default()
}
fn emit_log(
&self,
level: &str,
message: &str,
query_id: &str,
trace_id: &str,
context: serde_json::Value,
) {
let entry = TestLogEntry {
timestamp: Instant::now(),
level: level.to_string(),
message: message.to_string(),
query_id: query_id.to_string(),
trace_id: trace_id.to_string(),
context,
};
self.logs.lock().unwrap().push(entry);
}
fn get_logs_by_trace_id(&self, trace_id: &str) -> Vec<TestLogEntry> {
self.logs
.lock()
.unwrap()
.iter()
.filter(|l| l.trace_id == trace_id)
.cloned()
.collect()
}
fn _all_logs(&self) -> Vec<TestLogEntry> {
self.logs.lock().unwrap().clone()
}
}
struct TestFederationExecutor {
trace_context: TestTraceContext,
metrics: TestMetricsCollector,
logs: TestLogCollector,
}
impl TestFederationExecutor {
fn new(trace_id: String) -> Self {
let spans = Arc::new(Mutex::new(Vec::new()));
Self {
trace_context: TestTraceContext {
trace_id,
parent_span_id: format!("{:016x}", 1u64),
spans,
},
metrics: TestMetricsCollector::new(),
logs: TestLogCollector::new(),
}
}
fn create_span(&self, name: &str, attributes: HashMap<String, String>) -> TestSpan {
let span = TestSpan {
name: name.to_string(),
span_id: format!("{:016x}", rand::random::<u64>()),
_parent_span_id: self.trace_context.parent_span_id.clone(),
trace_id: self.trace_context.trace_id.clone(),
start_time: Instant::now(),
duration_us: 0,
attributes,
};
self.trace_context.spans.lock().unwrap().push(span.clone());
span
}
fn end_span(&self, span: &mut TestSpan) {
span.duration_us = span.start_time.elapsed().as_micros() as u64;
let mut spans = self.trace_context.spans.lock().unwrap();
if let Some(s) = spans.iter_mut().find(|s| s.span_id == span.span_id) {
s.duration_us = span.duration_us;
}
}
fn emit_log(&self, level: &str, message: &str, context: serde_json::Value) {
self.logs
.emit_log(level, message, "query_test", &self.trace_context.trace_id, context);
}
fn get_spans(&self) -> Vec<TestSpan> {
self.trace_context.spans.lock().unwrap().clone()
}
fn _get_span_tree(&self) -> String {
let spans = self.get_spans();
let mut tree = String::new();
tree.push_str(&format!(
"Root: {} (trace_id: {})\n",
"federation.query.execute",
&self.trace_context.trace_id[..8]
));
for span in &spans {
if span.name != "federation.query.execute" {
tree.push_str(&format!(
" └─ {}: {:.1}ms\n",
span.name,
span.duration_us as f64 / 1000.0
));
}
}
tree
}
}
#[test]
fn test_federation_query_complete_observability() {
println!("\n=== FEDERATION OBSERVABILITY INTEGRATION TEST ===\n");
let trace_id = "4bf92f3577b34da6a3ce929d0e0e4736".to_string();
let executor = TestFederationExecutor::new(trace_id.clone());
executor.emit_log(
"info",
"Entity resolution operation started",
json!({
"operation_type": "entity_resolution",
"entity_count": 3,
"entity_count_unique": 2,
"strategy": null,
}),
);
let mut entity_res_span = executor.create_span("federation.entity_resolution", {
let mut attrs = HashMap::new();
attrs.insert("query_id".to_string(), "query_test".to_string());
attrs.insert("entity_count".to_string(), "3".to_string());
attrs
});
executor.metrics.record_cache_miss();
std::thread::sleep(std::time::Duration::from_millis(32));
executor.metrics.record_entity_resolution(32_100);
executor.end_span(&mut entity_res_span);
executor.emit_log(
"info",
"Entity batch resolved",
json!({
"typename": "User",
"count": 2,
"strategy": "db",
}),
);
let mut subgraph_span_1 = executor.create_span("federation.subgraph_request", {
let mut attrs = HashMap::new();
attrs.insert("subgraph".to_string(), "users_subgraph".to_string());
attrs.insert("operation".to_string(), "_entities".to_string());
attrs
});
executor.metrics.record_cache_hit();
std::thread::sleep(std::time::Duration::from_millis(25));
executor.metrics.record_subgraph_request(25_300);
executor.end_span(&mut subgraph_span_1);
let mut subgraph_span_2 = executor.create_span("federation.subgraph_request", {
let mut attrs = HashMap::new();
attrs.insert("subgraph".to_string(), "posts_subgraph".to_string());
attrs.insert("operation".to_string(), "_entities".to_string());
attrs
});
executor.metrics.record_cache_miss();
std::thread::sleep(std::time::Duration::from_millis(19));
executor.metrics.record_subgraph_request(18_700);
executor.end_span(&mut subgraph_span_2);
executor.emit_log(
"info",
"Entity resolution operation completed",
json!({
"operation_type": "entity_resolution",
"status": "success",
"duration_ms": 75.1,
"resolved_count": 2,
"error_message": null,
}),
);
println!("Trace Analysis:");
let spans = executor.get_spans();
assert!(
spans.iter().any(|s| s.name == "federation.entity_resolution"),
"Entity resolution span missing"
);
let entity_span = spans.iter().find(|s| s.name == "federation.entity_resolution").unwrap();
assert_eq!(entity_span.trace_id, trace_id, "Entity span has wrong trace_id");
println!(
"✓ Entity resolution span: federation.entity_resolution (duration: {:.1}ms)",
entity_span.duration_us as f64 / 1000.0
);
let subgraph_spans: Vec<_> =
spans.iter().filter(|s| s.name == "federation.subgraph_request").collect();
assert_eq!(subgraph_spans.len(), 2, "Expected 2 subgraph request spans");
for (i, span) in subgraph_spans.iter().enumerate() {
let subgraph = span.attributes.get("subgraph").unwrap();
println!(
"✓ Subgraph span: federation.subgraph_request ({}, duration: {:.1}ms)",
subgraph,
span.duration_us as f64 / 1000.0
);
assert_eq!(span.trace_id, trace_id, "Subgraph span {} has wrong trace_id", i);
}
println!("\nMetrics Analysis:");
let metrics_json = executor.metrics.get_metrics_json();
assert_eq!(metrics_json["entity_resolutions_total"], 1, "Entity resolutions count mismatch");
println!(
"✓ federation_entity_resolutions_total: {}",
metrics_json["entity_resolutions_total"]
);
assert_eq!(metrics_json["subgraph_requests_total"], 2, "Subgraph requests count mismatch");
println!(
"✓ federation_subgraph_requests_total: {}",
metrics_json["subgraph_requests_total"]
);
assert_eq!(metrics_json["cache_hits"], 1, "Cache hits count mismatch");
println!("✓ federation_entity_cache_hits: {}", metrics_json["cache_hits"]);
assert_eq!(metrics_json["cache_misses"], 2, "Cache misses count mismatch");
println!("✓ federation_entity_cache_misses: {}", metrics_json["cache_misses"]);
println!("\nLogging Analysis:");
let logs = executor.logs.get_logs_by_trace_id(&trace_id);
assert!(!logs.is_empty(), "No logs emitted");
assert_eq!(logs.len(), 3, "Expected 3 log entries");
let started_log = logs.iter().find(|l| l.message.contains("started")).unwrap();
assert_eq!(started_log.trace_id, trace_id, "Started log has wrong trace_id");
println!("✓ Operation started: query_id=query_test, trace_id={}", &trace_id[..8]);
let batch_log = logs.iter().find(|l| l.message.contains("batch")).unwrap();
assert_eq!(batch_log.trace_id, trace_id, "Batch log has wrong trace_id");
println!("✓ Resolution batch: 3 entities deduplicated to 2 unique");
let completion_log = logs.iter().find(|l| l.message.contains("completed")).unwrap();
assert_eq!(completion_log.trace_id, trace_id, "Completion log has wrong trace_id");
println!("✓ Operation completed: 2 entities resolved, 0 errors");
println!("\nTrace Correlation:");
for log in &logs {
assert_eq!(log.trace_id, trace_id, "Log has mismatched trace_id");
}
println!("✓ All logs include trace_id for correlation");
println!("\nError Handling:");
assert_eq!(
executor.metrics.entity_resolutions_errors.lock().unwrap().clone(),
0,
"Unexpected entity resolution errors"
);
assert_eq!(
executor.metrics.subgraph_requests_errors.lock().unwrap().clone(),
0,
"Unexpected subgraph request errors"
);
println!("✓ No errors in observability pipeline");
println!("\n=== ALL VALIDATIONS PASSED ===\n");
}
#[test]
fn test_federation_mutation_with_observability() {
println!("\n=== FEDERATION MUTATION OBSERVABILITY TEST ===\n");
let trace_id = "a1b2c3d4e5f67890a1b2c3d4e5f67890".to_string();
let executor = TestFederationExecutor::new(trace_id.clone());
executor.emit_log(
"info",
"Mutation execution started",
json!({
"operation_type": "mutation_execute",
"query_id": "mutation_test",
"subgraph_count": 2,
}),
);
let mut mutation_span = executor.create_span("federation.mutation.execute", {
let mut attrs = HashMap::new();
attrs.insert("mutation_type".to_string(), "updateUserProfile".to_string());
attrs
});
std::thread::sleep(std::time::Duration::from_millis(45));
executor.metrics.record_mutation(45_200);
executor.end_span(&mut mutation_span);
executor.emit_log(
"info",
"Mutation execution completed",
json!({
"operation_type": "mutation_execute",
"status": "success",
"duration_ms": 45.2,
}),
);
println!("Mutation Analysis:");
let spans = executor.get_spans();
assert!(
spans.iter().any(|s| s.name == "federation.mutation.execute"),
"Mutation span missing"
);
println!("✓ Mutation span created");
let metrics_json = executor.metrics.get_metrics_json();
assert_eq!(metrics_json["mutations_total"], 1, "Mutation count mismatch");
println!("✓ federation_mutations_total: {}", metrics_json["mutations_total"]);
let logs = executor.logs.get_logs_by_trace_id(&trace_id);
assert_eq!(logs.len(), 2, "Expected 2 log entries for mutation");
println!("✓ Mutation logs emitted with trace_id correlation");
assert_eq!(
executor.metrics.mutations_errors.lock().unwrap().clone(),
0,
"Unexpected mutation errors"
);
println!("✓ No errors in mutation execution");
println!("\n=== MUTATION TEST PASSED ===\n");
}
#[test]
fn test_w3c_trace_context_propagation() {
println!("\n=== W3C TRACE CONTEXT PROPAGATION TEST ===\n");
let trace_id = "4bf92f3577b34da6a3ce929d0e0e4736".to_string();
let parent_span_id = "00f067aa0ba902b7".to_string();
let trace_flags = "01".to_string();
let traceparent = format!("00-{}-{}-{}", trace_id, parent_span_id, trace_flags);
println!("Generated Traceparent: {}", traceparent);
let parts: Vec<&str> = traceparent.split('-').collect();
assert_eq!(parts.len(), 4, "Traceparent format invalid");
assert_eq!(parts[0], "00", "Version should be 00");
assert_eq!(parts[1], trace_id, "Trace ID mismatch");
assert_eq!(parts[2], parent_span_id, "Parent span ID mismatch");
assert_eq!(parts[3], trace_flags, "Trace flags mismatch");
println!("✓ W3C Traceparent format valid");
println!("✓ Version: {}", parts[0]);
println!("✓ Trace ID: {}... (128-bit)", &parts[1][..8]);
println!("✓ Parent Span ID: {}... (64-bit)", &parts[2][..8]);
println!("✓ Trace Flags: {} (sampled)", parts[3]);
if let Some(recovered_traceparent) = parse_traceparent(&traceparent) {
assert_eq!(recovered_traceparent.trace_id, trace_id);
assert_eq!(recovered_traceparent.parent_span_id, parent_span_id);
println!("✓ Traceparent parsed and recovered successfully");
}
println!("\n=== TRACE CONTEXT PROPAGATION TEST PASSED ===\n");
}
#[test]
fn test_metrics_latency_recording() {
println!("\n=== METRICS LATENCY RECORDING TEST ===\n");
let collector = TestMetricsCollector::new();
let latencies = vec![32_100, 28_500, 35_200, 31_800, 29_400];
for latency in &latencies {
collector.record_entity_resolution(*latency);
}
let metrics = collector.get_metrics_json();
assert_eq!(metrics["entity_resolutions_total"], 5, "Count mismatch");
assert_eq!(metrics["entity_resolution_count"], 5, "Latency records mismatch");
println!("✓ Recorded {} entity resolutions", latencies.len());
let durations = collector.entity_resolution_durations.lock().unwrap().clone();
let avg_us = durations.iter().sum::<u64>() / durations.len() as u64;
let avg_ms = avg_us as f64 / 1000.0;
println!("✓ Average latency: {:.2}ms", avg_ms);
println!("✓ Min latency: {:.2}ms", *durations.iter().min().unwrap_or(&0) as f64 / 1000.0);
println!("✓ Max latency: {:.2}ms", *durations.iter().max().unwrap_or(&0) as f64 / 1000.0);
println!("\n=== LATENCY RECORDING TEST PASSED ===\n");
}
#[test]
fn test_structured_logging_json_serialization() {
println!("\n=== STRUCTURED LOGGING SERIALIZATION TEST ===\n");
let log_entry = TestLogEntry {
timestamp: Instant::now(),
level: "info".to_string(),
message: "Entity resolution completed".to_string(),
query_id: "query_123".to_string(),
trace_id: "4bf92f3577b34da6a3ce929d0e0e4736".to_string(),
context: json!({
"operation_type": "entity_resolution",
"status": "success",
"duration_ms": 32.5,
"resolved_count": 2,
"entity_count": 3,
}),
};
let json = json!({
"timestamp": log_entry.timestamp.elapsed().as_millis(),
"level": log_entry.level,
"message": log_entry.message,
"query_id": log_entry.query_id,
"trace_id": log_entry.trace_id,
"context": log_entry.context,
});
assert!(json.is_object(), "Log should serialize to JSON object");
assert!(json["trace_id"].is_string(), "trace_id should be string");
assert!(json["query_id"].is_string(), "query_id should be string");
assert!(json["context"].is_object(), "context should be object");
println!("✓ Log serialization valid");
println!("✓ Trace correlation fields present: trace_id, query_id");
println!("✓ JSON structure: {}", json);
println!("\n=== SERIALIZATION TEST PASSED ===\n");
}
fn parse_traceparent(header: &str) -> Option<TraceparentHeader> {
let parts: Vec<&str> = header.split('-').collect();
if parts.len() != 4 {
return None;
}
if parts[0] != "00" {
return None;
}
Some(TraceparentHeader {
trace_id: parts[1].to_string(),
parent_span_id: parts[2].to_string(),
_trace_flags: parts[3].to_string(),
})
}
#[derive(Debug)]
struct TraceparentHeader {
trace_id: String,
parent_span_id: String,
_trace_flags: String,
}