use netflow_parser::{NetflowParser, TemplateEvent, TemplateProtocol};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_hook_registration() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let _parser = NetflowParser::builder()
.on_template_event(move |_event| {
counter_clone.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.build()
.unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn test_multiple_hooks() {
let learned_count = Arc::new(AtomicUsize::new(0));
let collision_count = Arc::new(AtomicUsize::new(0));
let all_events_count = Arc::new(AtomicUsize::new(0));
let lc = learned_count.clone();
let cc = collision_count.clone();
let ac = all_events_count.clone();
let mut parser = NetflowParser::builder()
.on_template_event(move |event| {
match event {
TemplateEvent::Learned { .. } => {
lc.fetch_add(1, Ordering::SeqCst);
}
TemplateEvent::Collision { .. } => {
cc.fetch_add(1, Ordering::SeqCst);
}
_ => {}
}
Ok(())
})
.on_template_event(move |_event| {
ac.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(256),
protocol: TemplateProtocol::V9,
});
parser.trigger_template_event(TemplateEvent::Collision {
template_id: Some(300),
protocol: TemplateProtocol::Ipfix,
});
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(400),
protocol: TemplateProtocol::V9,
});
assert_eq!(learned_count.load(Ordering::SeqCst), 2);
assert_eq!(collision_count.load(Ordering::SeqCst), 1);
assert_eq!(all_events_count.load(Ordering::SeqCst), 3);
}
#[test]
fn test_hook_with_default_parser() {
let event_count = Arc::new(AtomicUsize::new(0));
let ec = event_count.clone();
let mut parser = NetflowParser::builder()
.on_template_event(move |_event| {
ec.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::MissingTemplate {
template_id: Some(500),
protocol: TemplateProtocol::Ipfix,
});
assert_eq!(event_count.load(Ordering::SeqCst), 1);
}
#[test]
fn test_hook_event_details() {
let captured_id = Arc::new(AtomicUsize::new(0));
let captured_protocol = Arc::new(std::sync::Mutex::new(None));
let cid = captured_id.clone();
let cp = captured_protocol.clone();
let mut parser = NetflowParser::builder()
.on_template_event(move |event| {
if let TemplateEvent::Evicted {
template_id,
protocol,
} = event
{
cid.store(template_id.unwrap_or(0) as usize, Ordering::SeqCst);
*cp.lock().unwrap() = Some(*protocol);
}
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::Evicted {
template_id: Some(1024),
protocol: TemplateProtocol::V9,
});
assert_eq!(captured_id.load(Ordering::SeqCst), 1024);
assert_eq!(
*captured_protocol.lock().unwrap(),
Some(TemplateProtocol::V9)
);
}
#[test]
fn test_hook_builder_chaining() {
let counter = Arc::new(AtomicUsize::new(0));
let c1 = counter.clone();
let c2 = counter.clone();
let mut parser = NetflowParser::builder()
.with_cache_size(2000)
.on_template_event(move |_| {
c1.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.with_allowed_versions(&[5, 9, 10])
.on_template_event(move |_| {
c2.fetch_add(10, Ordering::SeqCst);
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::Expired {
template_id: Some(200),
protocol: TemplateProtocol::Ipfix,
});
assert_eq!(counter.load(Ordering::SeqCst), 11);
}
#[test]
fn test_parser_without_hooks() {
let mut parser = NetflowParser::default();
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(100),
protocol: TemplateProtocol::V9,
});
}
#[test]
fn test_hook_with_logging() {
use std::sync::Mutex;
let log = Arc::new(Mutex::new(Vec::new()));
let log_clone = log.clone();
let mut parser = NetflowParser::builder()
.on_template_event(move |event| {
let msg = match event {
TemplateEvent::Learned {
template_id,
protocol,
} => format!("Learned template {:?} ({:?})", template_id, protocol),
TemplateEvent::Collision {
template_id,
protocol,
} => format!("Collision on template {:?} ({:?})", template_id, protocol),
TemplateEvent::Evicted {
template_id,
protocol,
} => format!("Evicted template {:?} ({:?})", template_id, protocol),
TemplateEvent::Expired {
template_id,
protocol,
} => format!("Expired template {:?} ({:?})", template_id, protocol),
TemplateEvent::MissingTemplate {
template_id,
protocol,
} => format!("Missing template {:?} ({:?})", template_id, protocol),
_ => "Unknown event".to_string(),
};
log_clone.lock().unwrap().push(msg);
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(256),
protocol: TemplateProtocol::V9,
});
parser.trigger_template_event(TemplateEvent::Collision {
template_id: Some(256),
protocol: TemplateProtocol::V9,
});
parser.trigger_template_event(TemplateEvent::MissingTemplate {
template_id: Some(300),
protocol: TemplateProtocol::Ipfix,
});
let logged = log.lock().unwrap();
assert_eq!(logged.len(), 3);
assert!(logged[0].contains("Learned template Some(256)"));
assert!(logged[1].contains("Collision on template Some(256)"));
assert!(logged[2].contains("Missing template Some(300)"));
}
#[test]
fn test_hooks_fire_during_parsing() {
use std::sync::Mutex;
let events = Arc::new(Mutex::new(Vec::<String>::new()));
let events_clone = events.clone();
let mut parser = NetflowParser::builder()
.on_template_event(move |event| {
let name = match event {
TemplateEvent::Learned { .. } => "Learned",
TemplateEvent::Collision { .. } => "Collision",
TemplateEvent::MissingTemplate { .. } => "MissingTemplate",
TemplateEvent::Evicted { .. } => "Evicted",
TemplateEvent::Expired { .. } => "Expired",
_ => "Unknown",
};
events_clone.lock().unwrap().push(name.to_string());
Ok(())
})
.build()
.unwrap();
let v9_template_packet: Vec<u8> = vec![
0, 9, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 1, 0,
1, 0, 4,
];
assert!(
!parser.parse_bytes(&v9_template_packet).packets.is_empty(),
"Template packet should parse successfully"
);
let captured = events.lock().unwrap();
assert!(
captured.iter().any(|e| e == "Learned"),
"Hook should fire a Learned event when parsing a V9 template. Got: {:?}",
*captured
);
}
#[test]
fn test_hook_error_count() {
let mut parser = NetflowParser::builder()
.on_template_event(|_| Err("intentional error".into()))
.build()
.unwrap();
assert_eq!(parser.hook_error_count(), 0);
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(256),
protocol: TemplateProtocol::V9,
});
assert_eq!(
parser.hook_error_count(),
1,
"hook_error_count should increment after a hook returns an error"
);
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(257),
protocol: TemplateProtocol::V9,
});
assert_eq!(
parser.hook_error_count(),
2,
"hook_error_count should accumulate across events"
);
}
#[test]
fn test_hook_error_isolation() {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();
let mut parser = NetflowParser::builder()
.on_template_event(|_| Err("hook 1 failed".into()))
.on_template_event(move |_| {
c.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.build()
.unwrap();
parser.trigger_template_event(TemplateEvent::Learned {
template_id: Some(256),
protocol: TemplateProtocol::V9,
});
assert_eq!(counter.load(Ordering::SeqCst), 1);
}