mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
757 lines
22 KiB
Rust
757 lines
22 KiB
Rust
use capture_logs::endpoints::datadog::*;
|
|
use serde_json::json;
|
|
use std::collections::HashMap;
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags() {
|
|
let result = parse_datadog_tags(Some("env:prod,region:us,version:1.2.3"));
|
|
assert_eq!(result.len(), 3);
|
|
assert_eq!(result.get("env").unwrap(), "\"prod\"");
|
|
assert_eq!(result.get("region").unwrap(), "\"us\"");
|
|
assert_eq!(result.get("version").unwrap(), "\"1.2.3\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_datadog_severity() {
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("emergency")),
|
|
("fatal".to_string(), 21)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("CRITICAL")),
|
|
("fatal".to_string(), 21)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("error")),
|
|
("error".to_string(), 17)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("warn")),
|
|
("warn".to_string(), 13)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("info")),
|
|
("info".to_string(), 9)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("debug")),
|
|
("debug".to_string(), 5)
|
|
);
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("trace")),
|
|
("trace".to_string(), 1)
|
|
);
|
|
assert_eq!(normalize_datadog_severity(None), ("info".to_string(), 9));
|
|
assert_eq!(
|
|
normalize_datadog_severity(Some("unknown")),
|
|
("info".to_string(), 9)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags_empty() {
|
|
let result = parse_datadog_tags(None);
|
|
assert!(result.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags_key_value() {
|
|
let result = parse_datadog_tags(Some("env:prod,region:us-east-1"));
|
|
assert_eq!(result.len(), 2);
|
|
assert_eq!(result.get("env").unwrap(), "\"prod\"");
|
|
assert_eq!(result.get("region").unwrap(), "\"us-east-1\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags_boolean() {
|
|
let result = parse_datadog_tags(Some("feature,enabled"));
|
|
assert_eq!(result.len(), 2);
|
|
assert_eq!(result.get("feature").unwrap(), "true");
|
|
assert_eq!(result.get("enabled").unwrap(), "true");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags_mixed() {
|
|
let result = parse_datadog_tags(Some("env:prod,feature,version:1.0.0"));
|
|
assert_eq!(result.len(), 3);
|
|
assert_eq!(result.get("env").unwrap(), "\"prod\"");
|
|
assert_eq!(result.get("feature").unwrap(), "true");
|
|
assert_eq!(result.get("version").unwrap(), "\"1.0.0\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_tags_with_spaces() {
|
|
let result = parse_datadog_tags(Some("env:prod , region:us-east-1 "));
|
|
assert_eq!(result.len(), 2);
|
|
assert_eq!(result.get("env").unwrap(), "\"prod\"");
|
|
assert_eq!(result.get("region").unwrap(), "\"us-east-1\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_with_dd_prefix() {
|
|
let mut extra = HashMap::new();
|
|
// Hex: "abc123" -> Base64: "q8Ej"
|
|
extra.insert("dd.trace_id".to_string(), json!("abc123"));
|
|
// Hex: "def456" -> Base64: "3vRW"
|
|
extra.insert("dd.span_id".to_string(), json!("def456"));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
assert_eq!(trace_id, "q8Ej");
|
|
assert_eq!(span_id, "3vRW");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_without_prefix() {
|
|
let mut extra = HashMap::new();
|
|
// Hex: "123abc" -> Base64: "Ejq8"
|
|
extra.insert("trace_id".to_string(), json!("123abc"));
|
|
// Hex: "456def" -> Base64: "RW3v"
|
|
extra.insert("span_id".to_string(), json!("456def"));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
assert_eq!(trace_id, "Ejq8");
|
|
assert_eq!(span_id, "RW3v");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_dd_prefix_takes_precedence() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("dd.trace_id".to_string(), json!("aabbcc"));
|
|
extra.insert("trace_id".to_string(), json!("112233"));
|
|
extra.insert("dd.span_id".to_string(), json!("ddeeff"));
|
|
extra.insert("span_id".to_string(), json!("445566"));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
// "aabbcc" hex -> base64
|
|
assert_eq!(trace_id, "qrvM");
|
|
// "ddeeff" hex -> base64
|
|
assert_eq!(span_id, "3e7/");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_empty() {
|
|
let extra = HashMap::new();
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
assert_eq!(trace_id, "");
|
|
assert_eq!(span_id, "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_non_string_values() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("dd.trace_id".to_string(), json!(12345));
|
|
extra.insert("dd.span_id".to_string(), json!(true));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
assert_eq!(trace_id, "");
|
|
assert_eq!(span_id, "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_with_0x_prefix() {
|
|
let mut extra = HashMap::new();
|
|
// 0x prefix should be stripped before decoding
|
|
extra.insert("dd.trace_id".to_string(), json!("0xaabbcc"));
|
|
extra.insert("dd.span_id".to_string(), json!("0xddeeff"));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
// Same result as without prefix
|
|
assert_eq!(trace_id, "qrvM");
|
|
assert_eq!(span_id, "3e7/");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_trace_span_ids_invalid_hex() {
|
|
let mut extra = HashMap::new();
|
|
// Invalid hex should return empty strings
|
|
extra.insert("dd.trace_id".to_string(), json!("not-valid-hex"));
|
|
extra.insert("dd.span_id".to_string(), json!("xyz123"));
|
|
|
|
let (trace_id, span_id) = extract_trace_span_ids(&extra);
|
|
assert_eq!(trace_id, "");
|
|
assert_eq!(span_id, "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_basic() {
|
|
let log = DatadogLog {
|
|
ddsource: Some("python".to_string()),
|
|
ddtags: Some("env:prod".to_string()),
|
|
hostname: Some("web-1".to_string()),
|
|
message: Some("Test log message".to_string()),
|
|
service: Some("my-service".to_string()),
|
|
status: Some("info".to_string()),
|
|
timestamp: Some(1234567890000),
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.body, "Test log message");
|
|
assert_eq!(row.service_name, "my-service");
|
|
assert_eq!(row.severity_text, "info");
|
|
assert_eq!(row.severity_number, 9);
|
|
assert!(row.resource_attributes.contains_key("service.name"));
|
|
assert!(row.resource_attributes.contains_key("host.name"));
|
|
assert!(row.resource_attributes.contains_key("ddsource"));
|
|
assert!(row.resource_attributes.contains_key("env"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_with_trace_ids() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("dd.trace_id".to_string(), json!("abcdefaa"));
|
|
extra.insert("dd.span_id".to_string(), json!("abcdefba"));
|
|
extra.insert("custom_field".to_string(), json!("custom_value"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.trace_id, "q83vqg==");
|
|
assert_eq!(row.span_id, "q83vug==");
|
|
assert!(row.attributes.contains_key("custom_field"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_query_params_override() {
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: None,
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let mut extra_params = HashMap::new();
|
|
extra_params.insert("custom".to_string(), "query_value".to_string());
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: Some("env:dev".to_string()),
|
|
ddsource: Some("query-source".to_string()),
|
|
service: Some("query-service".to_string()),
|
|
hostname: Some("query-host".to_string()),
|
|
message: Some("Query message".to_string()),
|
|
status: Some("warn".to_string()),
|
|
extra: extra_params,
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.body, "Query message");
|
|
assert_eq!(row.service_name, "query-service");
|
|
assert_eq!(row.severity_text, "warn");
|
|
assert!(row.attributes.contains_key("custom"));
|
|
assert!(row.resource_attributes.contains_key("env"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_body_takes_precedence() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("custom".to_string(), json!("body_value"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: Some("body-source".to_string()),
|
|
ddtags: Some("env:prod".to_string()),
|
|
hostname: Some("body-host".to_string()),
|
|
message: Some("Body message".to_string()),
|
|
service: Some("body-service".to_string()),
|
|
status: Some("error".to_string()),
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let mut extra_params = HashMap::new();
|
|
extra_params.insert("custom".to_string(), "query_value".to_string());
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: Some("env:dev".to_string()),
|
|
ddsource: Some("query-source".to_string()),
|
|
service: Some("query-service".to_string()),
|
|
hostname: Some("query-host".to_string()),
|
|
message: Some("Query message".to_string()),
|
|
status: Some("warn".to_string()),
|
|
extra: extra_params,
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.body, "Body message");
|
|
assert_eq!(row.service_name, "body-service");
|
|
assert_eq!(row.severity_text, "error");
|
|
assert_eq!(row.severity_number, 17);
|
|
// Body extra takes precedence over query extra
|
|
assert_eq!(row.attributes.get("custom").unwrap(), "\"body_value\"");
|
|
// Body ddtags take precedence over query ddtags
|
|
assert_eq!(row.resource_attributes.get("env").unwrap(), "\"prod\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_timestamp_default() {
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: None,
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
// Should have a timestamp (current time)
|
|
assert!(row.timestamp.timestamp() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_all_attributes_merged() {
|
|
let mut body_extra = HashMap::new();
|
|
body_extra.insert("body_attr".to_string(), json!("body_val"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: Some("body_tag:val1".to_string()),
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra: body_extra,
|
|
};
|
|
|
|
let mut query_extra = HashMap::new();
|
|
query_extra.insert("query_attr".to_string(), "query_val".to_string());
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: Some("query_tag:val2".to_string()),
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: query_extra,
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
// Should have all attributes from both sources
|
|
assert!(row.attributes.contains_key("query_attr"));
|
|
assert!(row.attributes.contains_key("body_attr"));
|
|
assert!(row.resource_attributes.contains_key("query_tag"));
|
|
assert!(row.resource_attributes.contains_key("body_tag"));
|
|
assert_eq!(row.attributes.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_event_name_extraction() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("event.name".to_string(), json!("user.signup"));
|
|
extra.insert("other_field".to_string(), json!("value"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.event_name, "user.signup");
|
|
assert!(row.attributes.contains_key("other_field"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_instrumentation_scope_extraction() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("otel.scope.name".to_string(), json!("my.service.logger"));
|
|
extra.insert("other_field".to_string(), json!("value"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.instrumentation_scope, "my.service.logger");
|
|
assert!(row.attributes.contains_key("other_field"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_event_name_and_scope_extraction() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("event.name".to_string(), json!("payment.processed"));
|
|
extra.insert("otel.scope.name".to_string(), json!("payment.service"));
|
|
extra.insert("custom_field".to_string(), json!("custom_value"));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Payment processed".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.event_name, "payment.processed");
|
|
assert_eq!(row.instrumentation_scope, "payment.service");
|
|
assert!(row.attributes.contains_key("custom_field"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_missing_event_name_and_scope() {
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.event_name, "");
|
|
assert_eq!(row.instrumentation_scope, "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_to_kafka_row_non_string_event_name_and_scope() {
|
|
let mut extra = HashMap::new();
|
|
extra.insert("event.name".to_string(), json!(12345));
|
|
extra.insert("otel.scope.name".to_string(), json!(true));
|
|
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra,
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
assert_eq!(row.event_name, "");
|
|
assert_eq!(row.instrumentation_scope, "");
|
|
}
|
|
|
|
// Tests for empty request handling - verifying empty arrays produce no kafka rows
|
|
|
|
#[test]
|
|
fn test_parse_empty_datadog_logs_array() {
|
|
// Empty array should parse successfully
|
|
let json_data = r#"[]"#;
|
|
let logs: Vec<DatadogLog> = serde_json::from_str(json_data).unwrap();
|
|
|
|
assert_eq!(logs.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_datadog_logs_produce_no_kafka_rows() {
|
|
// Simulates the logic in export_datadog_logs_http
|
|
let logs: Vec<DatadogLog> = vec![];
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test_token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let rows: Vec<_> = logs
|
|
.into_iter()
|
|
.map(|log| datadog_log_to_kafka_row(log, &query_params))
|
|
.collect();
|
|
|
|
assert_eq!(rows.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datadog_logs_single_vs_array() {
|
|
// Test that both single object and array formats work
|
|
let single_log = r#"{"message":"test"}"#;
|
|
let array_logs = r#"[{"message":"test"}]"#;
|
|
let empty_array = r#"[]"#;
|
|
|
|
// Single log parses as DatadogLog
|
|
let single: DatadogLog = serde_json::from_str(single_log).unwrap();
|
|
assert_eq!(single.message, Some("test".to_string()));
|
|
|
|
// Array parses as Vec<DatadogLog>
|
|
let array: Vec<DatadogLog> = serde_json::from_str(array_logs).unwrap();
|
|
assert_eq!(array.len(), 1);
|
|
assert_eq!(array[0].message, Some("test".to_string()));
|
|
|
|
// Empty array parses as empty Vec
|
|
let empty: Vec<DatadogLog> = serde_json::from_str(empty_array).unwrap();
|
|
assert_eq!(empty.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_minimal_fields() {
|
|
// Test that a log with all optional fields missing still parses
|
|
let json_data = r#"{}"#;
|
|
let log: DatadogLog = serde_json::from_str(json_data).unwrap();
|
|
|
|
assert!(log.ddsource.is_none());
|
|
assert!(log.ddtags.is_none());
|
|
assert!(log.hostname.is_none());
|
|
assert!(log.message.is_none());
|
|
assert!(log.service.is_none());
|
|
assert!(log.status.is_none());
|
|
assert!(log.timestamp.is_none());
|
|
assert!(log.extra.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_datadog_log_produces_valid_kafka_row() {
|
|
// Even an empty log should produce a valid KafkaLogRow with defaults
|
|
let log = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: None,
|
|
service: None,
|
|
status: None,
|
|
timestamp: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test_token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row, _) = datadog_log_to_kafka_row(log, &query_params);
|
|
|
|
// Should have default values
|
|
assert_eq!(row.body, "");
|
|
assert_eq!(row.service_name, "");
|
|
assert_eq!(row.severity_text, "info");
|
|
assert_eq!(row.severity_number, 9);
|
|
assert!(!row.uuid.is_empty()); // UUID should be generated
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_overridden_timestamp_tracking() {
|
|
use chrono::Utc;
|
|
|
|
// Test with a timestamp far in the past (should be overridden)
|
|
let far_past = Utc::now().timestamp_millis() - (48 * 3600 * 1000); // 48 hours ago
|
|
let log_overridden = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: Some(far_past),
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let query_params = DatadogQueryParams {
|
|
token: Some("test-token".to_string()),
|
|
ddtags: None,
|
|
ddsource: None,
|
|
service: None,
|
|
hostname: None,
|
|
message: None,
|
|
status: None,
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row_overridden, was_overridden) = datadog_log_to_kafka_row(log_overridden, &query_params);
|
|
assert!(was_overridden);
|
|
assert!(row_overridden.attributes.contains_key("$originalTimestamp"));
|
|
|
|
// Test with a recent timestamp (should not be overridden)
|
|
let recent = Utc::now().timestamp_millis() - (3600 * 1000); // 1 hour ago
|
|
let log_recent = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: Some(recent),
|
|
extra: HashMap::new(),
|
|
};
|
|
|
|
let (row_recent, was_not_overridden) = datadog_log_to_kafka_row(log_recent, &query_params);
|
|
assert!(!was_not_overridden);
|
|
assert!(!row_recent.attributes.contains_key("$originalTimestamp"));
|
|
|
|
// Test with user-provided originalTimestamp attribute (should not affect override detection)
|
|
let mut extra = HashMap::new();
|
|
extra.insert(
|
|
"originalTimestamp".to_string(),
|
|
json!("2020-01-01T00:00:00Z"),
|
|
);
|
|
let log_with_user_attr = DatadogLog {
|
|
ddsource: None,
|
|
ddtags: None,
|
|
hostname: None,
|
|
message: Some("Test".to_string()),
|
|
service: None,
|
|
status: None,
|
|
timestamp: Some(recent),
|
|
extra,
|
|
};
|
|
|
|
let (row_user_attr, was_not_overridden_2) =
|
|
datadog_log_to_kafka_row(log_with_user_attr, &query_params);
|
|
assert!(!was_not_overridden_2);
|
|
// User's originalTimestamp should still be in attributes
|
|
assert!(row_user_attr.attributes.contains_key("originalTimestamp"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_timestamp_as_numeric_string() {
|
|
let json_data = r#"{"timestamp": "1700000000000", "message": "hello"}"#;
|
|
let log: DatadogLog = serde_json::from_str(json_data).unwrap();
|
|
assert_eq!(log.timestamp, Some(1700000000000));
|
|
}
|
|
|
|
#[test]
|
|
fn test_datadog_log_timestamp_as_rfc3339_string() {
|
|
let json_data = r#"{"timestamp": "2024-01-15T10:30:00Z", "message": "hello"}"#;
|
|
let log: DatadogLog = serde_json::from_str(json_data).unwrap();
|
|
// 2024-01-15T10:30:00Z in milliseconds
|
|
assert_eq!(log.timestamp, Some(1705314600000));
|
|
}
|