use std::collections::HashMap;
use crate::observability::otel::{SpanKind, TestSpan, AttributeValue, SpanConformanceConfig};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_attribute_replace_not_append() {
let mut span = TestSpan::new("test_span", SpanKind::Internal);
span.set_attribute("service.name", "user-service-v1");
assert_eq!(span.attributes.get("service.name"), Some(&"user-service-v1".to_string()));
assert_eq!(span.attributes.len(), 1, "Should have exactly 1 attribute");
span.set_attribute("service.name", "user-service-v2");
assert_eq!(span.attributes.get("service.name"), Some(&"user-service-v2".to_string()));
assert_eq!(span.attributes.len(), 1, "Should still have exactly 1 attribute (replaced)");
let service_name_count = span.attributes.keys().filter(|&k| k == "service.name").count();
assert_eq!(service_name_count, 1, "Should have exactly 1 'service.name' key");
eprintln!("✅ SOUND: set_attribute() uses replace semantics");
eprintln!(" Initial: service.name = 'user-service-v1'");
eprintln!(" After replacement: service.name = 'user-service-v2'");
eprintln!(" Attribute count: 1 (no duplication)");
eprintln!(" OTLP spec compliance: ✅");
}
#[test]
fn test_set_attribute_multiple_replacements() {
let mut span = TestSpan::new("test_span", SpanKind::Internal);
span.set_attribute("http.method", "GET");
span.set_attribute("http.url", "https://api.example.com/v1");
span.set_attribute("user.id", "12345");
assert_eq!(span.attributes.len(), 3, "Should have 3 initial attributes");
span.set_attribute("http.method", "POST"); span.set_attribute("http.method", "PUT"); span.set_attribute("http.method", "PATCH");
assert_eq!(span.attributes.get("http.method"), Some(&"PATCH".to_string()));
assert_eq!(span.attributes.get("http.url"), Some(&"https://api.example.com/v1".to_string()));
assert_eq!(span.attributes.get("user.id"), Some(&"12345".to_string()));
assert_eq!(span.attributes.len(), 3, "Should still have exactly 3 attributes");
let method_count = span.attributes.keys().filter(|&k| k == "http.method").count();
assert_eq!(method_count, 1, "Should have exactly 1 'http.method' key");
eprintln!("✅ MULTIPLE REPLACEMENTS:");
eprintln!(" http.method: GET → POST → PUT → PATCH");
eprintln!(" Final value: PATCH (last-write-wins)");
eprintln!(" Attribute count unchanged: 3");
eprintln!(" No duplicate keys created");
}
#[test]
fn test_set_attribute_typed_variants_replace() {
let mut span = TestSpan::new("test_span", SpanKind::Internal);
span.set_attribute("operation.name", "initial_operation");
span.set_attribute("operation.name", "final_operation");
assert_eq!(span.attributes.get("operation.name"), Some(&"final_operation".to_string()));
span.set_int_attribute("retry.count", 1);
span.set_int_attribute("retry.count", 3);
assert_eq!(span.attribute_values.get("retry.count"), Some(&AttributeValue::Int(3)));
span.set_float_attribute("duration.seconds", 1.5);
span.set_float_attribute("duration.seconds", 2.7);
assert_eq!(span.attribute_values.get("duration.seconds"), Some(&AttributeValue::Float(2.7)));
span.set_bool_attribute("error.occurred", false);
span.set_bool_attribute("error.occurred", true);
assert_eq!(span.attribute_values.get("error.occurred"), Some(&AttributeValue::Bool(true)));
assert_eq!(span.attributes.len(), 4, "Should have 4 unique attributes");
assert_eq!(span.attribute_values.len(), 4, "Should have 4 unique typed attributes");
eprintln!("✅ TYPED ATTRIBUTE REPLACEMENTS:");
eprintln!(" String: operation.name = 'final_operation'");
eprintln!(" Integer: retry.count = 3");
eprintln!(" Float: duration.seconds = 2.7");
eprintln!(" Boolean: error.occurred = true");
eprintln!(" All use replace semantics (last-write-wins)");
}
#[test]
fn test_attribute_capacity_with_replacements() {
let config = SpanConformanceConfig {
max_attributes: 3, max_events: 10,
max_attribute_length: None,
test_sampling: false,
test_context_propagation: false,
};
let mut span = TestSpan::new_with_config("test_span", SpanKind::Internal, &config);
span.set_attribute("key1", "value1");
span.set_attribute("key2", "value2");
span.set_attribute("key3", "value3");
assert_eq!(span.attributes.len(), 3, "Should be at capacity");
assert_eq!(span.dropped_attributes_count, 0, "No attributes dropped yet");
span.set_attribute("key1", "new_value1");
span.set_attribute("key2", "new_value2");
span.set_attribute("key3", "new_value3");
assert_eq!(span.attributes.get("key1"), Some(&"new_value1".to_string()));
assert_eq!(span.attributes.get("key2"), Some(&"new_value2".to_string()));
assert_eq!(span.attributes.get("key3"), Some(&"new_value3".to_string()));
assert_eq!(span.attributes.len(), 3, "Still at capacity");
assert_eq!(span.dropped_attributes_count, 0, "No additional attributes dropped");
span.set_attribute("key4", "value4");
assert_eq!(span.attributes.len(), 3, "Still at capacity");
assert_eq!(span.dropped_attributes_count, 1, "New attribute should be dropped");
assert!(!span.attributes.contains_key("key4"), "key4 should not exist");
eprintln!("✅ CAPACITY BEHAVIOR:");
eprintln!(" Replacements at capacity: ✓ (allowed)");
eprintln!(" New keys at capacity: ✗ (dropped)");
eprintln!(" Dropped count tracks new keys only");
}
#[test]
fn test_no_duplicate_keys_in_final_state() {
let mut span = TestSpan::new("test_span", SpanKind::Internal);
let operations = vec![
("http.method", "GET"),
("http.url", "https://api.example.com"),
("user.id", "user123"),
("http.method", "POST"), ("service.name", "auth-service"),
("http.url", "https://auth.example.com"), ("trace.id", "trace456"),
("user.id", "user789"), ("http.method", "PUT"), ("service.version", "1.2.3"),
("trace.id", "trace999"), ];
for (key, value) in operations {
span.set_attribute(key, value);
}
let mut key_counts: HashMap<String, usize> = HashMap::new();
for key in span.attributes.keys() {
*key_counts.entry(key.clone()).or_insert(0) += 1;
}
for (key, count) in &key_counts {
assert_eq!(*count, 1, "Key '{}' appears {} times (should be 1)", key, count);
}
assert_eq!(span.attributes.get("http.method"), Some(&"PUT".to_string()));
assert_eq!(span.attributes.get("http.url"), Some(&"https://auth.example.com".to_string()));
assert_eq!(span.attributes.get("user.id"), Some(&"user789".to_string()));
assert_eq!(span.attributes.get("service.name"), Some(&"auth-service".to_string()));
assert_eq!(span.attributes.get("service.version"), Some(&"1.2.3".to_string()));
assert_eq!(span.attributes.get("trace.id"), Some(&"trace999".to_string()));
let unique_key_count = key_counts.len();
assert_eq!(span.attributes.len(), unique_key_count, "Attribute count should match unique keys");
eprintln!("✅ DUPLICATE KEY VERIFICATION:");
eprintln!(" Total attribute operations: {}", 11);
eprintln!(" Unique keys in final state: {}", unique_key_count);
eprintln!(" Duplicate keys found: 0");
eprintln!(" All values represent last-write-wins");
}
#[test]
fn test_otlp_spec_compliance_for_attribute_semantics() {
eprintln!("\n📋 OTLP ATTRIBUTE SEMANTICS SPECIFICATION");
eprintln!("=========================================");
eprintln!("Per OTLP specification:");
eprintln!(" • Attributes are key-value pairs with unique keys");
eprintln!(" • Multiple set_attribute() calls with same key should replace");
eprintln!(" • No duplicate keys should exist in final attribute map");
eprintln!(" • Attribute count reflects unique keys, not operations");
let mut span = TestSpan::new("compliance_test", SpanKind::Internal);
span.set_attribute("test.key", "value1");
let initial_count = span.attributes.len();
span.set_attribute("test.key", "value2"); let after_replace_count = span.attributes.len();
assert_eq!(initial_count, after_replace_count,
"Attribute count should not increase on replacement");
assert_eq!(span.attributes.get("test.key"), Some(&"value2".to_string()),
"Should have last-written value");
eprintln!("\n✅ OTLP SPECIFICATION COMPLIANCE:");
eprintln!(" ✓ Attributes have unique keys");
eprintln!(" ✓ set_attribute() uses replace semantics");
eprintln!(" ✓ No duplicate keys created");
eprintln!(" ✓ Attribute count reflects unique keys");
eprintln!(" ✓ Last-write-wins behavior");
eprintln!("\n🚫 WHAT WOULD BE WRONG (append semantics):");
eprintln!(" ✗ Creating duplicate keys: test.key=value1, test.key=value2");
eprintln!(" ✗ Concatenating values: test.key='value1,value2'");
eprintln!(" ✗ Increasing count on replacement operations");
eprintln!("\n✅ CURRENT IMPLEMENTATION: Replace semantics (CORRECT)");
}
#[test]
fn test_attribute_value_types_replace_consistently() {
let mut span = TestSpan::new("test_span", SpanKind::Internal);
span.set_attribute("flexible.value", "string_value");
assert_eq!(span.attributes.get("flexible.value"), Some(&"string_value".to_string()));
span.set_int_attribute("flexible.value", 42);
assert_eq!(span.attribute_values.get("flexible.value"), Some(&AttributeValue::Int(42)));
span.set_float_attribute("flexible.value", 3.14);
assert_eq!(span.attribute_values.get("flexible.value"), Some(&AttributeValue::Float(3.14)));
span.set_bool_attribute("flexible.value", true);
assert_eq!(span.attribute_values.get("flexible.value"), Some(&AttributeValue::Bool(true)));
span.set_attribute("flexible.value", "back_to_string");
assert_eq!(span.attributes.get("flexible.value"), Some(&"back_to_string".to_string()));
assert_eq!(span.attributes.len(), 1, "Should have 1 attribute in string map");
assert_eq!(span.attribute_values.len(), 1, "Should have 1 attribute in typed map");
eprintln!("✅ TYPE CHANGE REPLACEMENTS:");
eprintln!(" String → Int → Float → Bool → String");
eprintln!(" All replacements successful");
eprintln!(" Attribute count remains 1 throughout");
eprintln!(" Both string and typed maps updated consistently");
}
}