use super::*;
use crate::core::{EventKey, EventTimeExtractor, KeyExtractor, ProcessorEvent};
use crate::pipeline::StreamPipelineBuilder;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestEvent {
id: String,
value: f64,
timestamp: i64,
}
impl EventTimeExtractor<TestEvent> for TestEvent {
fn extract_event_time(&self, event: &TestEvent) -> DateTime<Utc> {
DateTime::from_timestamp_millis(event.timestamp).unwrap_or_else(Utc::now)
}
}
impl KeyExtractor<TestEvent> for TestEvent {
fn extract_key(&self, event: &TestEvent) -> EventKey {
EventKey::Session(event.id.clone())
}
}
#[test]
fn test_kafka_stream_stats_creation() {
let stats = KafkaStreamStats::new();
assert_eq!(stats.messages_consumed, 0);
assert_eq!(stats.messages_produced, 0);
assert_eq!(stats.consumer_lag, 0);
assert_eq!(stats.consume_errors, 0);
assert_eq!(stats.produce_errors, 0);
assert_eq!(stats.offsets_committed, 0);
assert_eq!(stats.commit_errors, 0);
assert!(stats.committed_offsets.is_empty());
}
#[test]
fn test_kafka_stream_stats_increments() {
let mut stats = KafkaStreamStats::new();
stats.inc_consumed();
assert_eq!(stats.messages_consumed, 1);
stats.inc_produced();
assert_eq!(stats.messages_produced, 1);
stats.inc_consume_errors();
assert_eq!(stats.consume_errors, 1);
stats.inc_produce_errors();
assert_eq!(stats.produce_errors, 1);
stats.inc_offsets_committed();
assert_eq!(stats.offsets_committed, 1);
stats.inc_commit_errors();
assert_eq!(stats.commit_errors, 1);
}
#[test]
fn test_kafka_stream_stats_offset_tracking() {
let mut stats = KafkaStreamStats::new();
stats.update_committed_offset(0, 100);
stats.update_committed_offset(1, 200);
stats.update_committed_offset(0, 150);
assert_eq!(stats.committed_offsets.get(&0), Some(&150));
assert_eq!(stats.committed_offsets.get(&1), Some(&200));
assert_eq!(stats.committed_offsets.get(&2), None);
}
#[test]
fn test_kafka_stream_config_default() {
let config = KafkaStreamConfig::default();
assert_eq!(config.brokers, "localhost:9092");
assert_eq!(config.group_id, "stream-processor");
assert_eq!(config.input_topics.len(), 1);
assert_eq!(config.input_topics[0], "input");
assert_eq!(config.output_topic, "output");
assert_eq!(config.auto_commit_interval_ms, 5000);
assert_eq!(config.poll_timeout_ms, 100);
assert_eq!(config.delivery_timeout_ms, 5000);
assert_eq!(config.max_produce_retries, 3);
assert!(config.consumer_config.is_empty());
assert!(config.producer_config.is_empty());
}
#[test]
fn test_kafka_stream_config_custom() {
let mut consumer_config = HashMap::new();
consumer_config.insert("session.timeout.ms".to_string(), "30000".to_string());
let mut producer_config = HashMap::new();
producer_config.insert("compression.type".to_string(), "lz4".to_string());
let config = KafkaStreamConfig {
brokers: "kafka:9093".to_string(),
group_id: "custom-group".to_string(),
input_topics: vec!["topic1".to_string(), "topic2".to_string()],
output_topic: "results".to_string(),
auto_commit_interval_ms: 10_000,
poll_timeout_ms: 200,
delivery_timeout_ms: 10_000,
max_produce_retries: 5,
consumer_config,
producer_config,
};
assert_eq!(config.brokers, "kafka:9093");
assert_eq!(config.group_id, "custom-group");
assert_eq!(config.input_topics.len(), 2);
assert_eq!(config.output_topic, "results");
assert_eq!(config.auto_commit_interval_ms, 10_000);
assert_eq!(config.max_produce_retries, 5);
assert_eq!(
config.consumer_config.get("session.timeout.ms"),
Some(&"30000".to_string())
);
assert_eq!(
config.producer_config.get("compression.type"),
Some(&"lz4".to_string())
);
}
#[test]
fn test_kafka_stream_stats_serialization() {
let mut stats = KafkaStreamStats::new();
stats.inc_consumed();
stats.inc_produced();
stats.update_committed_offset(0, 100);
let json = serde_json::to_string(&stats).unwrap();
let deserialized: KafkaStreamStats = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.messages_consumed, 1);
assert_eq!(deserialized.messages_produced, 1);
assert_eq!(deserialized.committed_offsets.get(&0), Some(&100));
}
#[tokio::test]
async fn test_test_event_extractors() {
let event = TestEvent {
id: "test-123".to_string(),
value: 42.5,
timestamp: 1699564800000,
};
let time = event.extract_event_time(&event);
assert_eq!(time.timestamp_millis(), 1699564800000);
let key = event.extract_key(&event);
match key {
EventKey::Session(id) => assert_eq!(id, "test-123"),
_ => panic!("Expected Session key"),
}
}
#[test]
fn test_kafka_stream_config_builder() {
let config = KafkaStreamConfig {
brokers: "localhost:9092,localhost:9093".to_string(),
group_id: "test-processor".to_string(),
input_topics: vec!["events".to_string()],
output_topic: "aggregates".to_string(),
auto_commit_interval_ms: 1000,
poll_timeout_ms: 50,
delivery_timeout_ms: 3000,
max_produce_retries: 10,
consumer_config: {
let mut cfg = HashMap::new();
cfg.insert("enable.partition.eof".to_string(), "true".to_string());
cfg
},
producer_config: {
let mut cfg = HashMap::new();
cfg.insert("acks".to_string(), "all".to_string());
cfg
},
};
assert!(config.brokers.contains("localhost:9092"));
assert!(config.brokers.contains("localhost:9093"));
assert_eq!(config.input_topics[0], "events");
assert_eq!(config.output_topic, "aggregates");
}
#[test]
fn test_kafka_stream_stats_clone() {
let mut stats1 = KafkaStreamStats::new();
stats1.inc_consumed();
stats1.inc_produced();
let stats2 = stats1.clone();
assert_eq!(stats2.messages_consumed, stats1.messages_consumed);
assert_eq!(stats2.messages_produced, stats1.messages_produced);
}
#[test]
fn test_kafka_stream_stats_default() {
let stats = KafkaStreamStats::default();
assert_eq!(stats.messages_consumed, 0);
assert_eq!(stats.messages_produced, 0);
}
#[test]
fn test_kafka_config_from_env_pattern() {
let brokers = std::env::var("KAFKA_BROKERS").unwrap_or_else(|_| "localhost:9092".to_string());
let group_id = std::env::var("KAFKA_GROUP_ID").unwrap_or_else(|_| "default-group".to_string());
assert_eq!(brokers, "localhost:9092");
assert_eq!(group_id, "default-group");
}
#[tokio::test]
async fn test_pipeline_integration_setup() {
let pipeline = StreamPipelineBuilder::new()
.with_name("test-kafka-pipeline")
.with_tumbling_window(60_000)
.build()
.unwrap();
let executor = pipeline.create_executor::<TestEvent>();
let event = TestEvent {
id: "test-1".to_string(),
value: 100.0,
timestamp: Utc::now().timestamp_millis(),
};
executor.ingest(event).await.unwrap();
let stats = executor.stats().await;
assert_eq!(stats.events_processed, 0); }
#[test]
fn test_kafka_config_validation() {
let config = KafkaStreamConfig::default();
assert!(!config.brokers.is_empty());
assert!(!config.group_id.is_empty());
assert!(!config.input_topics.is_empty());
assert!(!config.output_topic.is_empty());
assert!(config.auto_commit_interval_ms > 0);
assert!(config.poll_timeout_ms > 0);
assert!(config.delivery_timeout_ms > 0);
assert!(config.max_produce_retries > 0);
}
#[test]
fn test_multiple_input_topics() {
let config = KafkaStreamConfig {
input_topics: vec![
"topic-a".to_string(),
"topic-b".to_string(),
"topic-c".to_string(),
],
..Default::default()
};
assert_eq!(config.input_topics.len(), 3);
assert!(config.input_topics.contains(&"topic-a".to_string()));
assert!(config.input_topics.contains(&"topic-b".to_string()));
assert!(config.input_topics.contains(&"topic-c".to_string()));
}
#[tokio::test]
async fn test_stats_concurrent_updates() {
let stats = Arc::new(RwLock::new(KafkaStreamStats::new()));
let handles: Vec<_> = (0..10)
.map(|_| {
let stats = Arc::clone(&stats);
tokio::spawn(async move {
for _ in 0..100 {
stats.write().await.inc_consumed();
stats.write().await.inc_produced();
}
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
let final_stats = stats.read().await;
assert_eq!(final_stats.messages_consumed, 1000);
assert_eq!(final_stats.messages_produced, 1000);
}
#[test]
fn test_processor_event_with_test_event() {
let event = TestEvent {
id: "event-1".to_string(),
value: 42.0,
timestamp: 1699564800000,
};
let event_time = event.extract_event_time(&event);
let key = event.extract_key(&event);
let processor_event = ProcessorEvent::new(event, event_time, key.to_key_string());
assert_eq!(processor_event.event.id, "event-1");
assert_eq!(processor_event.event.value, 42.0);
assert!(processor_event.key.contains("session:event-1"));
}