1use std::collections::{HashMap, VecDeque};
28use std::future::Future;
29use std::pin::Pin;
30use std::sync::Arc;
31use std::task::{Context, Poll};
32
33use tokio::sync::{Mutex, Notify};
34use tower::Service;
35
36use camel_api::component_metadata::ComponentMetadata;
37use camel_component_api::UriConfig;
38use camel_component_api::parse_uri;
39use camel_component_api::{BoxProcessor, CamelError, Exchange};
40use camel_component_api::{Component, Consumer, Endpoint, ProducerContext, RuntimeObservability};
41use tracing::debug;
42
43const DEFAULT_MAX_RETAINED: usize = 10_000;
45
46#[derive(Clone, Debug)]
69pub struct MockConfig {
70 pub max_retained: usize,
73 pub copy_on_exchange: bool,
77 pub fail_fast: bool,
80 pub assert_period_ms: u64,
83 pub any_order: bool,
86}
87
88#[derive(Debug, Clone, UriConfig)]
93#[allow(dead_code)]
94#[uri_scheme = "mock"]
95#[uri_config(
96 skip_impl,
97 metadata(
98 scheme = "mock",
99 description = "Records exchanges for test assertions",
100 producer
101 ),
102 crate = "camel_component_api"
103)]
104struct MockUriConfig {
105 #[allow(dead_code)]
106 _name: String,
107}
108
109impl Default for MockConfig {
110 fn default() -> Self {
111 Self {
112 max_retained: DEFAULT_MAX_RETAINED,
113 copy_on_exchange: false,
114 fail_fast: false,
115 assert_period_ms: 0,
116 any_order: false,
117 }
118 }
119}
120
121impl MockConfig {
122 pub fn new(max_retained: usize) -> Self {
124 Self {
125 max_retained,
126 ..Self::default()
127 }
128 }
129
130 pub fn metadata() -> ComponentMetadata {
133 MockUriConfig::metadata()
134 }
135}
136
137pub struct MockExpectations {
147 expected_bodies: Vec<camel_component_api::Body>,
148 expected_headers: Vec<(String, serde_json::Value)>,
149 expected_header_regexes: Vec<(String, String)>,
150}
151
152impl Default for MockExpectations {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158impl MockExpectations {
159 pub fn new() -> Self {
161 Self {
162 expected_bodies: Vec::new(),
163 expected_headers: Vec::new(),
164 expected_header_regexes: Vec::new(),
165 }
166 }
167
168 pub fn push_body(&mut self, body: camel_component_api::Body) {
170 self.expected_bodies.push(body);
171 }
172
173 pub fn push_header(&mut self, key: String, value: serde_json::Value) {
175 self.expected_headers.push((key, value));
176 }
177
178 pub fn push_header_regex(&mut self, key: String, pattern: String) {
180 self.expected_header_regexes.push((key, pattern));
181 }
182}
183
184#[derive(Clone)]
199pub struct MockComponent {
200 registry: Arc<std::sync::Mutex<HashMap<String, Arc<MockEndpointInner>>>>,
201 config: MockConfig,
202}
203
204impl MockComponent {
205 pub fn new() -> Self {
206 Self::with_config(MockConfig::default())
207 }
208
209 pub fn with_config(config: MockConfig) -> Self {
211 Self {
212 registry: Arc::new(std::sync::Mutex::new(HashMap::new())),
213 config,
214 }
215 }
216
217 pub fn get_endpoint(&self, name: &str) -> Option<Arc<MockEndpointInner>> {
221 let registry = self
222 .registry
223 .lock()
224 .expect("mutex poisoned: another thread panicked while holding this lock"); registry.get(name).cloned()
226 }
227}
228
229impl Default for MockComponent {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl Component for MockComponent {
236 fn scheme(&self) -> &str {
237 "mock"
238 }
239
240 fn metadata(&self) -> ComponentMetadata {
241 MockConfig::metadata()
242 }
243
244 fn create_endpoint(
245 &self,
246 uri: &str,
247 _ctx: &dyn camel_component_api::ComponentContext,
248 ) -> Result<Box<dyn Endpoint>, CamelError> {
249 let parts = parse_uri(uri)?;
250 if parts.scheme != "mock" {
251 return Err(CamelError::InvalidUri(format!(
252 "expected scheme 'mock', got '{}'",
253 parts.scheme
254 )));
255 }
256
257 let name = parts.path;
258 if name.is_empty() {
259 return Err(CamelError::InvalidUri(
260 "mock endpoint name must be non-empty (use 'mock:<name>')".to_string(),
261 ));
262 }
263 let mut registry = self.registry.lock().map_err(|e| {
264 CamelError::EndpointCreationFailed(format!("mock registry lock poisoned: {e}"))
265 })?;
266 let max_retained = self.config.max_retained;
267 let copy_on_exchange = self.config.copy_on_exchange;
268 let fail_fast = self.config.fail_fast;
269 let assert_period_ms = self.config.assert_period_ms;
270 let any_order = self.config.any_order;
271 let inner = registry
272 .entry(name.clone())
273 .or_insert_with(|| {
274 Arc::new(MockEndpointInner {
275 uri: uri.to_string(),
276 name,
277 received: Arc::new(Mutex::new(VecDeque::new())),
278 notify: Arc::new(Notify::new()),
279 max_retained,
280 copy_on_exchange,
281 fail_fast,
282 fail_fast_error: Arc::new(std::sync::Mutex::new(None)),
283 assert_period_ms,
284 any_order,
285 expectations: Arc::new(std::sync::Mutex::new(MockExpectations::new())),
286 })
287 })
288 .clone();
289
290 debug!(endpoint_name = %inner.name, "mock endpoint created");
291 Ok(Box::new(MockEndpoint(inner)))
292 }
293}
294
295pub struct MockEndpoint(Arc<MockEndpointInner>);
305
306pub struct MockEndpointInner {
312 uri: String,
313 pub name: String,
314 received: Arc<Mutex<VecDeque<Exchange>>>,
315 notify: Arc<Notify>,
316 max_retained: usize,
317 copy_on_exchange: bool,
318 fail_fast: bool,
319 fail_fast_error: Arc<std::sync::Mutex<Option<CamelError>>>,
320 assert_period_ms: u64,
321 any_order: bool,
322 expectations: Arc<std::sync::Mutex<MockExpectations>>,
323}
324
325impl MockEndpointInner {
326 pub async fn get_received_exchanges(&self) -> Vec<Exchange> {
328 self.received.lock().await.iter().cloned().collect()
329 }
330
331 pub async fn received_count(&self) -> usize {
333 self.received.lock().await.len()
334 }
335
336 pub async fn reset(&self) {
340 self.received.lock().await.clear();
341 if let Ok(mut guard) = self.fail_fast_error.lock() {
342 *guard = None;
343 }
344 }
345
346 pub async fn assert_exchange_count(&self, expected: usize) {
352 let actual = self.received.lock().await.len();
353 assert_eq!(
354 actual, expected,
355 "MockEndpoint expected {expected} exchanges, got {actual}"
356 );
357 }
358
359 pub async fn await_exchanges(&self, count: usize, timeout: std::time::Duration) {
368 let deadline = tokio::time::Instant::now() + timeout;
369 loop {
370 {
371 let received = self.received.lock().await;
372 if received.len() >= count {
373 return;
374 }
375 }
376 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
377 if remaining.is_zero() {
378 let got = self.received.lock().await.len();
381 if got >= count {
382 return;
383 }
384 panic!(
385 "MockEndpoint '{}': timed out waiting for {} exchanges (got {} after {:?})",
386 self.name, count, got, timeout
387 );
388 }
389 tokio::select! {
390 _ = self.notify.notified() => {}
391 _ = tokio::time::sleep(remaining) => {}
392 }
393 }
394 }
395
396 pub async fn await_exchanges_with_timeout(&self, count: usize, fallback: std::time::Duration) {
401 let duration = if self.assert_period_ms > 0 {
402 std::time::Duration::from_millis(self.assert_period_ms)
403 } else {
404 fallback
405 };
406 self.await_exchanges(count, duration).await;
407 }
408
409 pub fn exchange(&self, idx: usize) -> ExchangeAssert {
423 let received = tokio::task::block_in_place(|| self.received.blocking_lock());
424 if idx >= received.len() {
425 panic!(
426 "MockEndpoint '{}': exchange index {} out of bounds (got {} exchanges)",
427 self.name,
428 idx,
429 received.len()
430 );
431 }
432 ExchangeAssert {
433 exchange: received[idx].clone(),
434 idx,
435 endpoint_name: self.name.clone(),
436 }
437 }
438
439 pub fn expect_body(&self, body: camel_component_api::Body) {
441 if let Ok(mut guard) = self.expectations.lock() {
442 guard.push_body(body);
443 }
444 }
445
446 pub fn expect_header(&self, key: &str, value: impl Into<serde_json::Value>) {
448 if let Ok(mut guard) = self.expectations.lock() {
449 guard.push_header(key.to_string(), value.into());
450 }
451 }
452
453 pub fn expect_header_regex(&self, key: &str, pattern: &str) {
458 if let Ok(mut guard) = self.expectations.lock() {
459 guard.push_header_regex(key.to_string(), pattern.to_string());
460 }
461 }
462
463 pub async fn assert_satisfied(&self) {
471 let received = self.get_received_exchanges().await;
472
473 {
475 let guard = self
476 .expectations
477 .lock()
478 .expect("expectations lock poisoned"); if !guard.expected_bodies.is_empty() {
480 let received_bodies: Vec<_> = received.iter().map(|e| &e.input.body).collect();
481 if guard.expected_bodies.len() != received_bodies.len() {
482 self.set_fail_fast_on_mismatch();
483 panic!(
484 "MockEndpoint '{}': expected {} bodies, got {}",
485 self.name,
486 guard.expected_bodies.len(),
487 received_bodies.len()
488 );
489 }
490 if self.any_order {
491 let mut unmatched: Vec<_> = received_bodies.iter().collect();
493 for expected in &guard.expected_bodies {
494 let idx = unmatched
495 .iter()
496 .position(|actual| body_eq(expected, actual));
497 match idx {
498 Some(i) => {
499 unmatched.remove(i);
500 }
501 None => {
502 self.set_fail_fast_on_mismatch();
503 panic!(
504 "MockEndpoint '{}': expected body {:?} not found in received exchanges (anyOrder mode)",
505 self.name, expected
506 );
507 }
508 }
509 }
510 } else {
511 for (i, expected) in guard.expected_bodies.iter().enumerate() {
512 if !body_eq(expected, received_bodies[i]) {
513 self.set_fail_fast_on_mismatch();
514 panic!(
515 "MockEndpoint '{}': body[{}] expected {:?}, got {:?}",
516 self.name, i, expected, received_bodies[i]
517 );
518 }
519 }
520 }
521 }
522
523 for (key, value) in &guard.expected_headers {
525 let found = received
526 .iter()
527 .any(|ex| ex.input.headers.get(key).is_some_and(|v| v == value));
528 if !found {
529 self.set_fail_fast_on_mismatch();
530 panic!(
531 "MockEndpoint '{}': expected header '{}' = {} not found in any received exchange",
532 self.name, key, value
533 );
534 }
535 }
536
537 for (key, pattern) in &guard.expected_header_regexes {
539 let re = regex::Regex::new(pattern).unwrap_or_else(|e| {
540 panic!(
541 "MockEndpoint '{}': invalid regex pattern {:?}: {e}",
542 self.name, pattern
543 )
544 });
545 let found = received.iter().any(|ex| {
546 ex.input.headers.get(key).is_some_and(|v| {
547 let s = match v {
548 serde_json::Value::String(s) => s.clone(),
549 other => other.to_string(),
550 };
551 re.is_match(&s)
552 })
553 });
554 if !found {
555 self.set_fail_fast_on_mismatch();
556 panic!(
557 "MockEndpoint '{}': no received exchange has header '{}' matching regex {:?}",
558 self.name, key, pattern
559 );
560 }
561 }
562 }
563 }
564
565 pub fn fail_fast_error(&self) -> Option<CamelError> {
567 self.fail_fast_error.lock().ok().and_then(|g| g.clone())
568 }
569
570 pub fn trigger_fail_fast(&self, error: CamelError) {
579 if let Ok(mut guard) = self.fail_fast_error.lock() {
580 *guard = Some(error);
581 }
582 }
583
584 fn set_fail_fast_on_mismatch(&self) {
590 if self.fail_fast
591 && let Ok(mut guard) = self.fail_fast_error.lock()
592 {
593 *guard = Some(CamelError::ProcessorError(
594 "assert_satisfied expectation mismatch".to_string(),
595 ));
596 }
597 }
598}
599
600fn body_eq(a: &camel_component_api::Body, b: &camel_component_api::Body) -> bool {
602 match (a, b) {
603 (camel_component_api::Body::Empty, camel_component_api::Body::Empty) => true,
604 (camel_component_api::Body::Text(a), camel_component_api::Body::Text(b)) => a == b,
605 (camel_component_api::Body::Json(a), camel_component_api::Body::Json(b)) => a == b,
606 (camel_component_api::Body::Xml(a), camel_component_api::Body::Xml(b)) => a == b,
607 (camel_component_api::Body::Bytes(a), camel_component_api::Body::Bytes(b)) => a == b,
608 _ => false,
609 }
610}
611
612impl Endpoint for MockEndpoint {
613 fn uri(&self) -> &str {
614 &self.0.uri
615 }
616
617 fn create_consumer(
618 &self,
619 _rt: Arc<dyn RuntimeObservability>,
620 ) -> Result<Box<dyn Consumer>, CamelError> {
621 Err(CamelError::EndpointCreationFailed(
622 "mock endpoint does not support consumers (it is a sink)".to_string(),
623 ))
624 }
625
626 fn create_producer(
627 &self,
628 _rt: Arc<dyn RuntimeObservability>,
629 _ctx: &ProducerContext,
630 ) -> Result<BoxProcessor, CamelError> {
631 Ok(BoxProcessor::new(MockProducer {
632 name: self.0.name.clone(),
633 received: Arc::clone(&self.0.received),
634 notify: Arc::clone(&self.0.notify),
635 max_retained: self.0.max_retained,
636 copy_on_exchange: self.0.copy_on_exchange,
637 fail_fast: self.0.fail_fast,
638 fail_fast_error: Arc::clone(&self.0.fail_fast_error),
639 }))
640 }
641}
642
643#[derive(Clone)]
649struct MockProducer {
650 name: String,
651 received: Arc<Mutex<VecDeque<Exchange>>>,
652 notify: Arc<Notify>,
653 max_retained: usize,
654 copy_on_exchange: bool,
655 fail_fast: bool,
656 fail_fast_error: Arc<std::sync::Mutex<Option<CamelError>>>,
657}
658
659impl Service<Exchange> for MockProducer {
660 type Response = Exchange;
661 type Error = CamelError;
662 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
663
664 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
665 if self.fail_fast
667 && let Ok(guard) = self.fail_fast_error.lock()
668 && guard.is_some()
669 {
670 return Poll::Ready(Err(CamelError::ProcessorError(
671 "mock endpoint in fail-fast mode: a previous exchange caused an error".to_string(),
672 )));
673 }
674 Poll::Ready(Ok(()))
675 }
676
677 fn call(&mut self, exchange: Exchange) -> Self::Future {
678 let name = self.name.clone();
679 let received = Arc::clone(&self.received);
680 let notify = Arc::clone(&self.notify);
681 let max_retained = self.max_retained;
682 let copy_on_exchange = self.copy_on_exchange;
683 let fail_fast = self.fail_fast;
684 let fail_fast_error = Arc::clone(&self.fail_fast_error);
685 Box::pin(async move {
686 if fail_fast
688 && let Ok(guard) = fail_fast_error.lock()
689 && guard.is_some()
690 {
691 return Err(CamelError::ProcessorError(
692 "mock endpoint in fail-fast mode: a previous exchange caused an error"
693 .to_string(),
694 ));
695 }
696
697 let correlation_id = exchange
698 .input
699 .headers
700 .get("CamelCorrelationId")
701 .and_then(|v| v.as_str())
702 .map(|s| s.to_string());
703
704 let exchange_to_store = if copy_on_exchange {
705 let mut cloned = exchange.clone();
706 cloned.input.body = clone_body(&exchange.input.body);
708 cloned
709 } else {
710 exchange.clone()
711 };
712
713 let mut guard = received.lock().await;
714 if guard.len() >= max_retained {
715 tracing::warn!(
716 endpoint_name = %name,
717 max = max_retained,
718 "max retained exchanges reached, dropping oldest"
719 );
720 guard.pop_front();
721 }
722 guard.push_back(exchange_to_store);
723 let count = guard.len();
724 drop(guard);
725
726 debug!(
727 endpoint_name = %name,
728 count = %count,
729 correlation_id = correlation_id.as_deref().unwrap_or("none"),
730 "exchange recorded on mock"
731 );
732 notify.notify_waiters();
733
734 Ok(exchange)
735 })
736 }
737}
738
739fn clone_body(body: &camel_component_api::Body) -> camel_component_api::Body {
741 match body {
742 camel_component_api::Body::Empty => camel_component_api::Body::Empty,
743 camel_component_api::Body::Text(s) => camel_component_api::Body::Text(s.clone()),
744 camel_component_api::Body::Json(v) => camel_component_api::Body::Json(v.clone()),
745 camel_component_api::Body::Xml(s) => camel_component_api::Body::Xml(s.clone()),
746 camel_component_api::Body::Bytes(b) => camel_component_api::Body::Bytes(b.clone()),
747 camel_component_api::Body::Stream(s) => camel_component_api::Body::Stream(s.clone()),
748 _ => camel_component_api::Body::Empty,
751 }
752}
753
754pub struct ExchangeAssert {
766 exchange: Exchange,
767 idx: usize,
768 endpoint_name: String,
769}
770
771impl ExchangeAssert {
772 fn location(&self) -> String {
773 format!(
774 "MockEndpoint '{}' exchange[{}]",
775 self.endpoint_name, self.idx
776 )
777 }
778
779 pub fn assert_body_text(self, expected: &str) -> Self {
781 match self.exchange.input.body.as_text() {
782 Some(actual) if actual == expected => {}
783 Some(actual) => panic!(
784 "{}: expected body text {:?}, got {:?}",
785 self.location(),
786 expected,
787 actual
788 ),
789 None => panic!(
790 "{}: expected body text {:?}, but body is not Body::Text (got {:?})",
791 self.location(),
792 expected,
793 self.exchange.input.body
794 ),
795 }
796 self
797 }
798
799 pub fn assert_body_json(self, expected: serde_json::Value) -> Self {
801 match &self.exchange.input.body {
802 camel_component_api::Body::Json(actual) if *actual == expected => {}
803 camel_component_api::Body::Json(actual) => panic!(
804 "{}: expected body JSON {}, got {}",
805 self.location(),
806 expected,
807 actual
808 ),
809 other => panic!(
810 "{}: expected body JSON {}, but body is not Body::Json (got {:?})",
811 self.location(),
812 expected,
813 other
814 ),
815 }
816 self
817 }
818
819 pub fn assert_body_bytes(self, expected: &[u8]) -> Self {
821 match &self.exchange.input.body {
822 camel_component_api::Body::Bytes(actual) if actual.as_ref() == expected => {}
823 camel_component_api::Body::Bytes(actual) => panic!(
824 "{}: expected body bytes {:?}, got {:?}",
825 self.location(),
826 expected,
827 actual
828 ),
829 other => panic!(
830 "{}: expected body bytes {:?}, but body is not Body::Bytes (got {:?})",
831 self.location(),
832 expected,
833 other
834 ),
835 }
836 self
837 }
838
839 pub fn assert_header(self, key: &str, expected: serde_json::Value) -> Self {
845 match self.exchange.input.headers.get(key) {
846 Some(actual) if *actual == expected => {}
847 Some(actual) => panic!(
848 "{}: expected header {:?} = {}, got {}",
849 self.location(),
850 key,
851 expected,
852 actual
853 ),
854 None => panic!(
855 "{}: expected header {:?} = {}, but header is absent",
856 self.location(),
857 key,
858 expected
859 ),
860 }
861 self
862 }
863
864 pub fn assert_header_exists(self, key: &str) -> Self {
870 if !self.exchange.input.headers.contains_key(key) {
871 panic!(
872 "{}: expected header {:?} to be present, but it was absent",
873 self.location(),
874 key
875 );
876 }
877 self
878 }
879
880 pub fn assert_has_error(self) -> Self {
886 if self.exchange.error.is_none() {
887 panic!(
888 "{}: expected exchange to have an error, but error is None",
889 self.location()
890 );
891 }
892 self
893 }
894
895 pub fn assert_no_error(self) -> Self {
901 if let Some(ref err) = self.exchange.error {
902 panic!(
903 "{}: expected exchange to have no error, but got: {}",
904 self.location(),
905 err
906 );
907 }
908 self
909 }
910}
911
912#[cfg(test)]
917mod tests {
918 use camel_component_api::test_support::PanicRuntimeObservability;
919 fn rt() -> std::sync::Arc<dyn camel_component_api::RuntimeObservability> {
920 std::sync::Arc::new(PanicRuntimeObservability)
921 }
922
923 use super::*;
924 use camel_component_api::Message;
925 use camel_component_api::NoOpComponentContext;
926 use tower::ServiceExt;
927
928 fn test_producer_ctx() -> ProducerContext {
929 ProducerContext::new()
930 }
931
932 #[test]
933 fn test_mock_component_scheme() {
934 let component = MockComponent::new();
935 assert_eq!(component.scheme(), "mock");
936 }
937
938 #[test]
939 fn test_mock_component_default() {
940 let component = MockComponent::default();
941 assert_eq!(component.scheme(), "mock");
942 assert!(component.get_endpoint("missing").is_none());
943 }
944
945 #[test]
946 fn test_mock_creates_endpoint() {
947 let component = MockComponent::new();
948 let endpoint = component.create_endpoint("mock:result", &NoOpComponentContext);
949 assert!(endpoint.is_ok());
950 }
951
952 #[test]
953 fn test_mock_wrong_scheme() {
954 let component = MockComponent::new();
955 let result = component.create_endpoint("timer:tick", &NoOpComponentContext);
956 assert!(result.is_err());
957 }
958
959 #[test]
960 fn test_empty_mock_endpoint_name_rejected() {
961 let component = MockComponent::new();
962 let result = component.create_endpoint("mock:", &NoOpComponentContext);
963 assert!(result.is_err(), "empty mock name should be rejected");
964 }
965
966 #[test]
967 fn test_valid_mock_endpoint_name_accepted() {
968 let component = MockComponent::new();
969 let result = component.create_endpoint("mock:result", &NoOpComponentContext);
970 assert!(result.is_ok());
971 }
972
973 #[test]
974 fn test_mock_endpoint_no_consumer() {
975 let component = MockComponent::new();
976 let endpoint = component
977 .create_endpoint("mock:result", &NoOpComponentContext)
978 .unwrap();
979 assert!(endpoint.create_consumer(rt()).is_err());
980 }
981
982 #[test]
983 fn test_mock_endpoint_creates_producer() {
984 let ctx = test_producer_ctx();
985 let component = MockComponent::new();
986 let endpoint = component
987 .create_endpoint("mock:result", &NoOpComponentContext)
988 .unwrap();
989 assert!(endpoint.create_producer(rt(), &ctx).is_ok());
990 }
991
992 #[test]
993 fn test_mock_endpoint_uri() {
994 let component = MockComponent::new();
995 let endpoint = component
996 .create_endpoint("mock:uri-check", &NoOpComponentContext)
997 .unwrap();
998 assert_eq!(endpoint.uri(), "mock:uri-check");
999 }
1000
1001 #[test]
1002 fn test_mock_get_endpoint_returns_same_inner_for_same_name() {
1003 let component = MockComponent::new();
1004 let _ = component
1005 .create_endpoint("mock:shared-inner", &NoOpComponentContext)
1006 .unwrap();
1007 let _ = component
1008 .create_endpoint("mock:shared-inner", &NoOpComponentContext)
1009 .unwrap();
1010
1011 let first = component.get_endpoint("shared-inner").unwrap();
1012 let second = component.get_endpoint("shared-inner").unwrap();
1013 assert!(Arc::ptr_eq(&first, &second));
1014 }
1015
1016 #[tokio::test]
1017 async fn test_mock_producer_records_exchange() {
1018 let ctx = test_producer_ctx();
1019 let component = MockComponent::new();
1020 let endpoint = component
1021 .create_endpoint("mock:test", &NoOpComponentContext)
1022 .unwrap();
1023
1024 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1025
1026 let ex1 = Exchange::new(Message::new("first"));
1027 let ex2 = Exchange::new(Message::new("second"));
1028
1029 producer.call(ex1).await.unwrap();
1030 producer.call(ex2).await.unwrap();
1031
1032 let inner = component.get_endpoint("test").unwrap();
1033 inner.assert_exchange_count(2).await;
1034
1035 let received = inner.get_received_exchanges().await;
1036 assert_eq!(received[0].input.body.as_text(), Some("first"));
1037 assert_eq!(received[1].input.body.as_text(), Some("second"));
1038 }
1039
1040 #[tokio::test]
1041 async fn test_mock_producer_passes_through_exchange() {
1042 let ctx = test_producer_ctx();
1043 let component = MockComponent::new();
1044 let endpoint = component
1045 .create_endpoint("mock:passthrough", &NoOpComponentContext)
1046 .unwrap();
1047
1048 let producer = endpoint.create_producer(rt(), &ctx).unwrap();
1049 let exchange = Exchange::new(Message::new("hello"));
1050 let result = producer.oneshot(exchange).await.unwrap();
1051
1052 assert_eq!(result.input.body.as_text(), Some("hello"));
1054 }
1055
1056 #[tokio::test]
1057 async fn test_mock_assert_count_passes() {
1058 let component = MockComponent::new();
1059 let endpoint = component
1060 .create_endpoint("mock:count", &NoOpComponentContext)
1061 .unwrap();
1062 let inner = component.get_endpoint("count").unwrap();
1063
1064 inner.assert_exchange_count(0).await;
1065
1066 let ctx = test_producer_ctx();
1067 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1068 producer
1069 .call(Exchange::new(Message::new("one")))
1070 .await
1071 .unwrap();
1072
1073 inner.assert_exchange_count(1).await;
1074 }
1075
1076 #[tokio::test]
1077 #[should_panic(expected = "MockEndpoint expected 5 exchanges, got 0")]
1078 async fn test_mock_assert_count_fails() {
1079 let component = MockComponent::new();
1080 let _endpoint = component
1083 .create_endpoint("mock:fail", &NoOpComponentContext)
1084 .unwrap();
1085 let inner = component.get_endpoint("fail").unwrap();
1086
1087 inner.assert_exchange_count(5).await;
1088 }
1089
1090 #[tokio::test]
1091 async fn test_mock_component_shared_registry() {
1092 let component = MockComponent::new();
1093 let ep1 = component
1094 .create_endpoint("mock:shared", &NoOpComponentContext)
1095 .unwrap();
1096 let ep2 = component
1097 .create_endpoint("mock:shared", &NoOpComponentContext)
1098 .unwrap();
1099
1100 let ctx = test_producer_ctx();
1102 let mut p1 = ep1.create_producer(rt(), &ctx).unwrap();
1103 p1.call(Exchange::new(Message::new("from-ep1")))
1104 .await
1105 .unwrap();
1106
1107 let mut p2 = ep2.create_producer(rt(), &ctx).unwrap();
1109 p2.call(Exchange::new(Message::new("from-ep2")))
1110 .await
1111 .unwrap();
1112
1113 let inner = component.get_endpoint("shared").unwrap();
1115 inner.assert_exchange_count(2).await;
1116
1117 let received = inner.get_received_exchanges().await;
1118 assert_eq!(received[0].input.body.as_text(), Some("from-ep1"));
1119 assert_eq!(received[1].input.body.as_text(), Some("from-ep2"));
1120 }
1121
1122 #[tokio::test]
1123 async fn await_exchanges_resolves_immediately() {
1124 let ctx = test_producer_ctx();
1126 let component = MockComponent::new();
1127 let endpoint = component
1128 .create_endpoint("mock:immediate", &NoOpComponentContext)
1129 .unwrap();
1130 let inner = component.get_endpoint("immediate").unwrap();
1131
1132 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1133 producer
1134 .call(Exchange::new(Message::new("a")))
1135 .await
1136 .unwrap();
1137 producer
1138 .call(Exchange::new(Message::new("b")))
1139 .await
1140 .unwrap();
1141
1142 inner
1144 .await_exchanges(2, std::time::Duration::from_millis(100))
1145 .await;
1146 }
1147
1148 #[tokio::test]
1149 async fn await_exchanges_waits_then_resolves() {
1150 let ctx = test_producer_ctx();
1152 let component = MockComponent::new();
1153 let endpoint = component
1154 .create_endpoint("mock:waiter", &NoOpComponentContext)
1155 .unwrap();
1156 let inner = component.get_endpoint("waiter").unwrap();
1157
1158 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1160 tokio::spawn(async move {
1161 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1162 producer
1163 .call(Exchange::new(Message::new("delayed")))
1164 .await
1165 .unwrap();
1166 });
1167
1168 inner
1170 .await_exchanges(1, std::time::Duration::from_millis(500))
1171 .await;
1172
1173 let received = inner.get_received_exchanges().await;
1174 assert_eq!(received.len(), 1);
1175 assert_eq!(received[0].input.body.as_text(), Some("delayed"));
1176 }
1177
1178 #[tokio::test]
1179 #[should_panic(expected = "timed out waiting for 5 exchanges")]
1180 async fn await_exchanges_times_out() {
1181 let component = MockComponent::new();
1182 let _endpoint = component
1183 .create_endpoint("mock:timeout", &NoOpComponentContext)
1184 .unwrap();
1185 let inner = component.get_endpoint("timeout").unwrap();
1186
1187 inner
1189 .await_exchanges(5, std::time::Duration::from_millis(50))
1190 .await;
1191 }
1192
1193 #[tokio::test(flavor = "multi_thread")]
1194 async fn exchange_idx_returns_assert() {
1195 let ctx = test_producer_ctx();
1196 let component = MockComponent::new();
1197 let endpoint = component
1198 .create_endpoint("mock:assert-idx", &NoOpComponentContext)
1199 .unwrap();
1200 let inner = component.get_endpoint("assert-idx").unwrap();
1201
1202 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1203 producer
1204 .call(Exchange::new(Message::new("hello")))
1205 .await
1206 .unwrap();
1207
1208 inner
1209 .await_exchanges(1, std::time::Duration::from_millis(500))
1210 .await;
1211 let _assert = inner.exchange(0);
1213 }
1214
1215 #[tokio::test(flavor = "multi_thread")]
1216 #[should_panic(expected = "exchange index 5 out of bounds")]
1217 async fn exchange_idx_out_of_bounds() {
1218 let ctx = test_producer_ctx();
1219 let component = MockComponent::new();
1220 let endpoint = component
1221 .create_endpoint("mock:oob", &NoOpComponentContext)
1222 .unwrap();
1223 let inner = component.get_endpoint("oob").unwrap();
1224
1225 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1226 producer
1227 .call(Exchange::new(Message::new("only-one")))
1228 .await
1229 .unwrap();
1230
1231 inner
1232 .await_exchanges(1, std::time::Duration::from_millis(500))
1233 .await;
1234 let _assert = inner.exchange(5);
1236 }
1237
1238 #[tokio::test(flavor = "multi_thread")]
1239 async fn assert_body_text_pass() {
1240 let ctx = test_producer_ctx();
1241 let component = MockComponent::new();
1242 let endpoint = component
1243 .create_endpoint("mock:body-text-pass", &NoOpComponentContext)
1244 .unwrap();
1245 let inner = component.get_endpoint("body-text-pass").unwrap();
1246 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1247 producer
1248 .call(Exchange::new(Message::new("hello")))
1249 .await
1250 .unwrap();
1251 inner
1252 .await_exchanges(1, std::time::Duration::from_millis(500))
1253 .await;
1254 inner.exchange(0).assert_body_text("hello");
1255 }
1256
1257 #[tokio::test(flavor = "multi_thread")]
1258 #[should_panic(expected = "expected body text")]
1259 async fn assert_body_text_fail() {
1260 let ctx = test_producer_ctx();
1261 let component = MockComponent::new();
1262 let endpoint = component
1263 .create_endpoint("mock:body-text-fail", &NoOpComponentContext)
1264 .unwrap();
1265 let inner = component.get_endpoint("body-text-fail").unwrap();
1266 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1267 producer
1268 .call(Exchange::new(Message::new("hello")))
1269 .await
1270 .unwrap();
1271 inner
1272 .await_exchanges(1, std::time::Duration::from_millis(500))
1273 .await;
1274 inner.exchange(0).assert_body_text("world");
1275 }
1276
1277 #[tokio::test(flavor = "multi_thread")]
1278 async fn assert_body_json_pass() {
1279 use camel_component_api::Body;
1280 let ctx = test_producer_ctx();
1281 let component = MockComponent::new();
1282 let endpoint = component
1283 .create_endpoint("mock:body-json-pass", &NoOpComponentContext)
1284 .unwrap();
1285 let inner = component.get_endpoint("body-json-pass").unwrap();
1286 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1287 let mut msg = Message::new("");
1288 msg.body = Body::Json(serde_json::json!({"key": "value"}));
1289 producer.call(Exchange::new(msg)).await.unwrap();
1290 inner
1291 .await_exchanges(1, std::time::Duration::from_millis(500))
1292 .await;
1293 inner
1294 .exchange(0)
1295 .assert_body_json(serde_json::json!({"key": "value"}));
1296 }
1297
1298 #[tokio::test(flavor = "multi_thread")]
1299 #[should_panic(expected = "expected body JSON")]
1300 async fn assert_body_json_fail() {
1301 use camel_component_api::Body;
1302 let ctx = test_producer_ctx();
1303 let component = MockComponent::new();
1304 let endpoint = component
1305 .create_endpoint("mock:body-json-fail", &NoOpComponentContext)
1306 .unwrap();
1307 let inner = component.get_endpoint("body-json-fail").unwrap();
1308 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1309 let mut msg = Message::new("");
1310 msg.body = Body::Json(serde_json::json!({"key": "value"}));
1311 producer.call(Exchange::new(msg)).await.unwrap();
1312 inner
1313 .await_exchanges(1, std::time::Duration::from_millis(500))
1314 .await;
1315 inner
1316 .exchange(0)
1317 .assert_body_json(serde_json::json!({"key": "other"}));
1318 }
1319
1320 #[tokio::test(flavor = "multi_thread")]
1321 async fn assert_body_bytes_pass() {
1322 use bytes::Bytes;
1323 use camel_component_api::Body;
1324 let ctx = test_producer_ctx();
1325 let component = MockComponent::new();
1326 let endpoint = component
1327 .create_endpoint("mock:body-bytes-pass", &NoOpComponentContext)
1328 .unwrap();
1329 let inner = component.get_endpoint("body-bytes-pass").unwrap();
1330 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1331 let mut msg = Message::new("");
1332 msg.body = Body::Bytes(Bytes::from_static(b"binary"));
1333 producer.call(Exchange::new(msg)).await.unwrap();
1334 inner
1335 .await_exchanges(1, std::time::Duration::from_millis(500))
1336 .await;
1337 inner.exchange(0).assert_body_bytes(b"binary");
1338 }
1339
1340 #[tokio::test(flavor = "multi_thread")]
1341 #[should_panic(expected = "expected body bytes")]
1342 async fn assert_body_bytes_fail() {
1343 use bytes::Bytes;
1344 use camel_component_api::Body;
1345 let ctx = test_producer_ctx();
1346 let component = MockComponent::new();
1347 let endpoint = component
1348 .create_endpoint("mock:body-bytes-fail", &NoOpComponentContext)
1349 .unwrap();
1350 let inner = component.get_endpoint("body-bytes-fail").unwrap();
1351 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1352 let mut msg = Message::new("");
1353 msg.body = Body::Bytes(Bytes::from_static(b"binary"));
1354 producer.call(Exchange::new(msg)).await.unwrap();
1355 inner
1356 .await_exchanges(1, std::time::Duration::from_millis(500))
1357 .await;
1358 inner.exchange(0).assert_body_bytes(b"different");
1359 }
1360
1361 #[tokio::test(flavor = "multi_thread")]
1362 async fn assert_header_pass() {
1363 let ctx = test_producer_ctx();
1364 let component = MockComponent::new();
1365 let endpoint = component
1366 .create_endpoint("mock:hdr-pass", &NoOpComponentContext)
1367 .unwrap();
1368 let inner = component.get_endpoint("hdr-pass").unwrap();
1369 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1370 let mut msg = Message::new("body");
1371 msg.headers
1372 .insert("x-key".to_string(), serde_json::json!("value"));
1373 producer.call(Exchange::new(msg)).await.unwrap();
1374 inner
1375 .await_exchanges(1, std::time::Duration::from_millis(500))
1376 .await;
1377 inner
1378 .exchange(0)
1379 .assert_header("x-key", serde_json::json!("value"));
1380 }
1381
1382 #[tokio::test(flavor = "multi_thread")]
1383 #[should_panic(expected = "expected header")]
1384 async fn assert_header_fail() {
1385 let ctx = test_producer_ctx();
1386 let component = MockComponent::new();
1387 let endpoint = component
1388 .create_endpoint("mock:hdr-fail", &NoOpComponentContext)
1389 .unwrap();
1390 let inner = component.get_endpoint("hdr-fail").unwrap();
1391 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1392 let mut msg = Message::new("body");
1393 msg.headers
1394 .insert("x-key".to_string(), serde_json::json!("value"));
1395 producer.call(Exchange::new(msg)).await.unwrap();
1396 inner
1397 .await_exchanges(1, std::time::Duration::from_millis(500))
1398 .await;
1399 inner
1400 .exchange(0)
1401 .assert_header("x-key", serde_json::json!("other"));
1402 }
1403
1404 #[tokio::test(flavor = "multi_thread")]
1405 async fn assert_header_exists_pass() {
1406 let ctx = test_producer_ctx();
1407 let component = MockComponent::new();
1408 let endpoint = component
1409 .create_endpoint("mock:hdr-exists-pass", &NoOpComponentContext)
1410 .unwrap();
1411 let inner = component.get_endpoint("hdr-exists-pass").unwrap();
1412 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1413 let mut msg = Message::new("body");
1414 msg.headers
1415 .insert("x-present".to_string(), serde_json::json!(42));
1416 producer.call(Exchange::new(msg)).await.unwrap();
1417 inner
1418 .await_exchanges(1, std::time::Duration::from_millis(500))
1419 .await;
1420 inner.exchange(0).assert_header_exists("x-present");
1421 }
1422
1423 #[tokio::test(flavor = "multi_thread")]
1424 #[should_panic(expected = "expected header")]
1425 async fn assert_header_exists_fail() {
1426 let ctx = test_producer_ctx();
1427 let component = MockComponent::new();
1428 let endpoint = component
1429 .create_endpoint("mock:hdr-exists-fail", &NoOpComponentContext)
1430 .unwrap();
1431 let inner = component.get_endpoint("hdr-exists-fail").unwrap();
1432 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1433 producer
1434 .call(Exchange::new(Message::new("body")))
1435 .await
1436 .unwrap();
1437 inner
1438 .await_exchanges(1, std::time::Duration::from_millis(500))
1439 .await;
1440 inner.exchange(0).assert_header_exists("x-missing");
1441 }
1442
1443 #[tokio::test(flavor = "multi_thread")]
1444 async fn assert_has_error_pass() {
1445 let ctx = test_producer_ctx();
1446 let component = MockComponent::new();
1447 let endpoint = component
1448 .create_endpoint("mock:err-pass", &NoOpComponentContext)
1449 .unwrap();
1450 let inner = component.get_endpoint("err-pass").unwrap();
1451 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1452 let mut ex = Exchange::new(Message::new("body"));
1453 ex.set_error(camel_component_api::CamelError::ProcessorError(
1454 "oops".to_string(),
1455 ));
1456 producer.call(ex).await.unwrap();
1457 inner
1458 .await_exchanges(1, std::time::Duration::from_millis(500))
1459 .await;
1460 inner.exchange(0).assert_has_error();
1461 }
1462
1463 #[tokio::test(flavor = "multi_thread")]
1464 #[should_panic(expected = "expected exchange to have an error")]
1465 async fn assert_has_error_fail() {
1466 let ctx = test_producer_ctx();
1467 let component = MockComponent::new();
1468 let endpoint = component
1469 .create_endpoint("mock:has-err-fail", &NoOpComponentContext)
1470 .unwrap();
1471 let inner = component.get_endpoint("has-err-fail").unwrap();
1472 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1473 producer
1474 .call(Exchange::new(Message::new("body")))
1475 .await
1476 .unwrap();
1477 inner
1478 .await_exchanges(1, std::time::Duration::from_millis(500))
1479 .await;
1480 inner.exchange(0).assert_has_error();
1481 }
1482
1483 #[tokio::test(flavor = "multi_thread")]
1484 async fn assert_no_error_pass() {
1485 let ctx = test_producer_ctx();
1486 let component = MockComponent::new();
1487 let endpoint = component
1488 .create_endpoint("mock:no-err-pass", &NoOpComponentContext)
1489 .unwrap();
1490 let inner = component.get_endpoint("no-err-pass").unwrap();
1491 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1492 producer
1493 .call(Exchange::new(Message::new("body")))
1494 .await
1495 .unwrap();
1496 inner
1497 .await_exchanges(1, std::time::Duration::from_millis(500))
1498 .await;
1499 inner.exchange(0).assert_no_error();
1500 }
1501
1502 #[tokio::test]
1507 async fn test_mock_reset_clears_exchanges() {
1508 let component = MockComponent::new();
1509 let endpoint = component
1510 .create_endpoint("mock:reset-test", &NoOpComponentContext)
1511 .unwrap();
1512 let inner = component.get_endpoint("reset-test").unwrap();
1513
1514 let ctx = test_producer_ctx();
1515 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1516 producer
1517 .call(Exchange::new(Message::new("a")))
1518 .await
1519 .unwrap();
1520 producer
1521 .call(Exchange::new(Message::new("b")))
1522 .await
1523 .unwrap();
1524
1525 assert_eq!(inner.received_count().await, 2);
1526 inner.reset().await;
1527 assert_eq!(inner.received_count().await, 0);
1528 }
1529
1530 #[tokio::test]
1531 async fn test_mock_bounded_retention_drops_oldest() {
1532 let config = MockConfig {
1533 max_retained: 3,
1534 ..Default::default()
1535 };
1536 let component = MockComponent::with_config(config);
1537 let endpoint = component
1538 .create_endpoint("mock:bounded", &NoOpComponentContext)
1539 .unwrap();
1540 let inner = component.get_endpoint("bounded").unwrap();
1541
1542 let ctx = test_producer_ctx();
1543 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1544
1545 for i in 0..5 {
1547 producer
1548 .call(Exchange::new(Message::new(format!("msg-{i}"))))
1549 .await
1550 .unwrap();
1551 }
1552
1553 assert_eq!(inner.received_count().await, 3);
1554 let received = inner.get_received_exchanges().await;
1555 assert_eq!(received[0].input.body.as_text(), Some("msg-2"));
1557 assert_eq!(received[1].input.body.as_text(), Some("msg-3"));
1558 assert_eq!(received[2].input.body.as_text(), Some("msg-4"));
1559 }
1560
1561 #[tokio::test]
1562 async fn test_mock_reset_then_record_again() {
1563 let component = MockComponent::new();
1564 let endpoint = component
1565 .create_endpoint("mock:reset-reuse", &NoOpComponentContext)
1566 .unwrap();
1567 let inner = component.get_endpoint("reset-reuse").unwrap();
1568
1569 let ctx = test_producer_ctx();
1570 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1571 producer
1572 .call(Exchange::new(Message::new("before-reset")))
1573 .await
1574 .unwrap();
1575 inner.reset().await;
1576
1577 producer
1578 .call(Exchange::new(Message::new("after-reset")))
1579 .await
1580 .unwrap();
1581
1582 let received = inner.get_received_exchanges().await;
1583 assert_eq!(received.len(), 1);
1584 assert_eq!(received[0].input.body.as_text(), Some("after-reset"));
1585 }
1586
1587 #[tokio::test(flavor = "multi_thread")]
1588 #[should_panic(expected = "expected exchange to have no error")]
1589 async fn assert_no_error_fail() {
1590 let ctx = test_producer_ctx();
1591 let component = MockComponent::new();
1592 let endpoint = component
1593 .create_endpoint("mock:no-err-fail", &NoOpComponentContext)
1594 .unwrap();
1595 let inner = component.get_endpoint("no-err-fail").unwrap();
1596 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1597 let mut ex = Exchange::new(Message::new("body"));
1598 ex.set_error(camel_component_api::CamelError::ProcessorError(
1599 "oops".to_string(),
1600 ));
1601 producer.call(ex).await.unwrap();
1602 inner
1603 .await_exchanges(1, std::time::Duration::from_millis(500))
1604 .await;
1605 inner.exchange(0).assert_no_error();
1606 }
1607
1608 #[tokio::test]
1613 async fn test_copy_on_exchange_stores_cloned_body() {
1614 let config = MockConfig {
1615 copy_on_exchange: true,
1616 ..Default::default()
1617 };
1618 let component = MockComponent::with_config(config);
1619 let endpoint = component
1620 .create_endpoint("mock:copy", &NoOpComponentContext)
1621 .unwrap();
1622 let inner = component.get_endpoint("copy").unwrap();
1623
1624 let ctx = test_producer_ctx();
1625 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1626
1627 let mut msg = Message::new("original");
1628 msg.headers.insert("x-test".into(), serde_json::json!(1));
1629 let ex = Exchange::new(msg);
1630 producer.call(ex).await.unwrap();
1631
1632 let received = inner.get_received_exchanges().await;
1633 assert_eq!(received[0].input.body.as_text(), Some("original"));
1634 }
1635
1636 #[tokio::test]
1637 async fn test_copy_on_exchange_false_shares_storage() {
1638 let config = MockConfig {
1639 copy_on_exchange: false,
1640 ..Default::default()
1641 };
1642 let component = MockComponent::with_config(config);
1643 let endpoint = component
1644 .create_endpoint("mock:no-copy", &NoOpComponentContext)
1645 .unwrap();
1646 let inner = component.get_endpoint("no-copy").unwrap();
1647
1648 let ctx = test_producer_ctx();
1649 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1650
1651 producer
1652 .call(Exchange::new(Message::new("direct")))
1653 .await
1654 .unwrap();
1655
1656 let received = inner.get_received_exchanges().await;
1657 assert_eq!(received[0].input.body.as_text(), Some("direct"));
1658 }
1659
1660 #[tokio::test]
1665 async fn test_clone_body_preserves_stream() {
1666 use bytes::Bytes;
1667 use camel_component_api::{Body, StreamBody, StreamMetadata};
1668 use futures::stream;
1669 use std::sync::Arc;
1670 use tokio::sync::Mutex;
1671
1672 let chunks: Vec<Result<Bytes, camel_component_api::CamelError>> =
1673 vec![Ok(Bytes::from("data"))];
1674 let body = Body::Stream(StreamBody {
1675 stream: Arc::new(Mutex::new(Some(Box::pin(stream::iter(chunks))))),
1676 metadata: StreamMetadata::default(),
1677 });
1678
1679 let config = MockConfig {
1680 copy_on_exchange: true,
1681 ..Default::default()
1682 };
1683 let component = MockComponent::with_config(config);
1684 let endpoint = component
1685 .create_endpoint("mock:stream-test", &NoOpComponentContext)
1686 .unwrap();
1687 let inner = component.get_endpoint("stream-test").unwrap();
1688
1689 let ctx = test_producer_ctx();
1690 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1691
1692 let msg = Message::new(body);
1693 let ex = Exchange::new(msg);
1694 producer.call(ex).await.unwrap();
1695
1696 let received = inner.get_received_exchanges().await;
1697 assert!(
1698 matches!(received[0].input.body, Body::Stream(_)),
1699 "expected Body::Stream, got {:?}",
1700 received[0].input.body
1701 );
1702 }
1703
1704 #[tokio::test]
1705 async fn test_clone_body_stream_shares_arc() {
1706 use bytes::Bytes;
1707 use camel_component_api::{Body, StreamBody, StreamMetadata};
1708 use futures::stream;
1709 use std::sync::Arc;
1710 use tokio::sync::Mutex;
1711
1712 let chunks: Vec<Result<Bytes, camel_component_api::CamelError>> =
1713 vec![Ok(Bytes::from("data"))];
1714 let original = Body::Stream(StreamBody {
1715 stream: Arc::new(Mutex::new(Some(Box::pin(stream::iter(chunks))))),
1716 metadata: StreamMetadata::default(),
1717 });
1718
1719 let clone = clone_body(&original);
1720
1721 let _ = original.into_bytes(100).await.unwrap();
1723
1724 let result = clone.into_bytes(100).await;
1726 assert!(
1727 matches!(
1728 result,
1729 Err(camel_component_api::CamelError::AlreadyConsumed)
1730 ),
1731 "expected AlreadyConsumed, got {:?}",
1732 result
1733 );
1734 }
1735
1736 #[tokio::test]
1741 async fn test_assert_satisfied_bodies_in_order() {
1742 let component = MockComponent::new();
1743 let endpoint = component
1744 .create_endpoint("mock:sat-bodies", &NoOpComponentContext)
1745 .unwrap();
1746 let inner = component.get_endpoint("sat-bodies").unwrap();
1747
1748 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1749 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1750
1751 let ctx = test_producer_ctx();
1752 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1753 producer
1754 .call(Exchange::new(Message::new("alpha")))
1755 .await
1756 .unwrap();
1757 producer
1758 .call(Exchange::new(Message::new("beta")))
1759 .await
1760 .unwrap();
1761
1762 inner.assert_satisfied().await;
1763 }
1764
1765 #[tokio::test]
1766 #[should_panic(expected = "body[0] expected")]
1767 async fn test_assert_satisfied_bodies_wrong_order_fails() {
1768 let component = MockComponent::new();
1769 let endpoint = component
1770 .create_endpoint("mock:sat-bodies-fail", &NoOpComponentContext)
1771 .unwrap();
1772 let inner = component.get_endpoint("sat-bodies-fail").unwrap();
1773
1774 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1775 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1776
1777 let ctx = test_producer_ctx();
1778 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1779 producer
1780 .call(Exchange::new(Message::new("beta")))
1781 .await
1782 .unwrap();
1783 producer
1784 .call(Exchange::new(Message::new("alpha")))
1785 .await
1786 .unwrap();
1787
1788 inner.assert_satisfied().await;
1789 }
1790
1791 #[tokio::test]
1792 async fn test_assert_satisfied_headers() {
1793 let component = MockComponent::new();
1794 let endpoint = component
1795 .create_endpoint("mock:sat-hdr", &NoOpComponentContext)
1796 .unwrap();
1797 let inner = component.get_endpoint("sat-hdr").unwrap();
1798
1799 inner.expect_header("status", serde_json::json!("ok"));
1800
1801 let ctx = test_producer_ctx();
1802 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1803 let mut msg = Message::new("body");
1804 msg.headers.insert("status".into(), serde_json::json!("ok"));
1805 producer.call(Exchange::new(msg)).await.unwrap();
1806
1807 inner.assert_satisfied().await;
1808 }
1809
1810 #[tokio::test]
1811 #[should_panic(expected = "expected header 'missing' =")]
1812 async fn test_assert_satisfied_headers_missing() {
1813 let component = MockComponent::new();
1814 let endpoint = component
1815 .create_endpoint("mock:sat-hdr-missing", &NoOpComponentContext)
1816 .unwrap();
1817 let inner = component.get_endpoint("sat-hdr-missing").unwrap();
1818
1819 inner.expect_header("missing", serde_json::json!("value"));
1820
1821 let ctx = test_producer_ctx();
1822 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1823 producer
1824 .call(Exchange::new(Message::new("body")))
1825 .await
1826 .unwrap();
1827
1828 inner.assert_satisfied().await;
1829 }
1830
1831 #[tokio::test]
1836 async fn test_fail_fast_rejects_after_first_call() {
1837 let config = MockConfig {
1838 fail_fast: true,
1839 ..Default::default()
1840 };
1841 let component = MockComponent::with_config(config);
1842 let endpoint = component
1843 .create_endpoint("mock:ff", &NoOpComponentContext)
1844 .unwrap();
1845
1846 let ctx = test_producer_ctx();
1847 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1848
1849 producer
1851 .call(Exchange::new(Message::new("ok")))
1852 .await
1853 .unwrap();
1854 }
1855
1856 #[tokio::test]
1857 async fn test_fail_fast_no_error_when_all_good() {
1858 let config = MockConfig {
1859 fail_fast: true,
1860 ..Default::default()
1861 };
1862 let component = MockComponent::with_config(config);
1863 let endpoint = component
1864 .create_endpoint("mock:ff-good", &NoOpComponentContext)
1865 .unwrap();
1866 let inner = component.get_endpoint("ff-good").unwrap();
1867
1868 let ctx = test_producer_ctx();
1869 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1870
1871 producer
1872 .call(Exchange::new(Message::new("a")))
1873 .await
1874 .unwrap();
1875 producer
1876 .call(Exchange::new(Message::new("b")))
1877 .await
1878 .unwrap();
1879
1880 assert!(inner.fail_fast_error().is_none());
1881 inner.assert_exchange_count(2).await;
1882 }
1883
1884 #[tokio::test]
1889 async fn test_await_exchanges_with_timeout_uses_config_period() {
1890 let config = MockConfig {
1891 assert_period_ms: 100,
1892 ..Default::default()
1893 };
1894 let component = MockComponent::with_config(config);
1895 let endpoint = component
1896 .create_endpoint("mock:ap", &NoOpComponentContext)
1897 .unwrap();
1898 let inner = component.get_endpoint("ap").unwrap();
1899
1900 let ctx = test_producer_ctx();
1901 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1902 producer
1903 .call(Exchange::new(Message::new("x")))
1904 .await
1905 .unwrap();
1906
1907 inner
1908 .await_exchanges_with_timeout(1, std::time::Duration::from_millis(1))
1909 .await;
1910 }
1911
1912 #[tokio::test]
1913 async fn test_await_exchanges_with_timeout_uses_fallback_when_zero() {
1914 let config = MockConfig {
1915 assert_period_ms: 0,
1916 ..Default::default()
1917 };
1918 let component = MockComponent::with_config(config);
1919 let endpoint = component
1920 .create_endpoint("mock:ap-fb", &NoOpComponentContext)
1921 .unwrap();
1922 let inner = component.get_endpoint("ap-fb").unwrap();
1923
1924 let ctx = test_producer_ctx();
1925 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1926 producer
1927 .call(Exchange::new(Message::new("y")))
1928 .await
1929 .unwrap();
1930
1931 inner
1932 .await_exchanges_with_timeout(1, std::time::Duration::from_millis(200))
1933 .await;
1934 }
1935
1936 #[tokio::test]
1941 async fn test_expect_header_regex_match() {
1942 let component = MockComponent::new();
1943 let endpoint = component
1944 .create_endpoint("mock:re-hdr", &NoOpComponentContext)
1945 .unwrap();
1946 let inner = component.get_endpoint("re-hdr").unwrap();
1947
1948 inner.expect_header_regex("x-trace-id", r"^[a-f0-9]{8}$");
1949
1950 let ctx = test_producer_ctx();
1951 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1952 let mut msg = Message::new("body");
1953 msg.headers
1954 .insert("x-trace-id".into(), serde_json::json!("deadbeef"));
1955 producer.call(Exchange::new(msg)).await.unwrap();
1956
1957 inner.assert_satisfied().await;
1958 }
1959
1960 #[tokio::test]
1961 #[should_panic(expected = "no received exchange has header")]
1962 async fn test_expect_header_regex_no_match() {
1963 let component = MockComponent::new();
1964 let endpoint = component
1965 .create_endpoint("mock:re-hdr-fail", &NoOpComponentContext)
1966 .unwrap();
1967 let inner = component.get_endpoint("re-hdr-fail").unwrap();
1968
1969 inner.expect_header_regex("x-trace-id", r"^\d+$");
1970
1971 let ctx = test_producer_ctx();
1972 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1973 let mut msg = Message::new("body");
1974 msg.headers
1975 .insert("x-trace-id".into(), serde_json::json!("abc"));
1976 producer.call(Exchange::new(msg)).await.unwrap();
1977
1978 inner.assert_satisfied().await;
1979 }
1980
1981 #[tokio::test]
1986 async fn test_any_order_bodies_match() {
1987 let config = MockConfig {
1988 any_order: true,
1989 ..Default::default()
1990 };
1991 let component = MockComponent::with_config(config);
1992 let endpoint = component
1993 .create_endpoint("mock:anyorder", &NoOpComponentContext)
1994 .unwrap();
1995 let inner = component.get_endpoint("anyorder").unwrap();
1996
1997 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1998 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1999
2000 let ctx = test_producer_ctx();
2001 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2002 producer
2003 .call(Exchange::new(Message::new("alpha")))
2004 .await
2005 .unwrap();
2006 producer
2007 .call(Exchange::new(Message::new("beta")))
2008 .await
2009 .unwrap();
2010
2011 inner.assert_satisfied().await;
2012 }
2013
2014 #[tokio::test]
2015 #[should_panic(expected = "not found in received exchanges (anyOrder mode)")]
2016 async fn test_any_order_bodies_missing() {
2017 let config = MockConfig {
2018 any_order: true,
2019 ..Default::default()
2020 };
2021 let component = MockComponent::with_config(config);
2022 let endpoint = component
2023 .create_endpoint("mock:anyorder-fail", &NoOpComponentContext)
2024 .unwrap();
2025 let inner = component.get_endpoint("anyorder-fail").unwrap();
2026
2027 inner.expect_body(camel_component_api::Body::Text("gamma".into()));
2028 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
2029
2030 let ctx = test_producer_ctx();
2031 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2032 producer
2033 .call(Exchange::new(Message::new("alpha")))
2034 .await
2035 .unwrap();
2036 producer
2037 .call(Exchange::new(Message::new("beta")))
2038 .await
2039 .unwrap();
2040
2041 inner.assert_satisfied().await;
2042 }
2043
2044 #[tokio::test]
2049 async fn test_tracing_logs_exchange_received() {
2050 let ctx = test_producer_ctx();
2052 let component = MockComponent::new();
2053 let endpoint = component
2054 .create_endpoint("mock:trace", &NoOpComponentContext)
2055 .unwrap();
2056 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2057 producer
2058 .call(Exchange::new(Message::new("traced")))
2059 .await
2060 .unwrap();
2061
2062 let inner = component.get_endpoint("trace").unwrap();
2063 inner.assert_exchange_count(1).await;
2064 }
2065
2066 #[test]
2071 fn test_mock_config_new() {
2072 let cfg = MockConfig::new(42);
2073 assert_eq!(cfg.max_retained, 42);
2074 assert!(!cfg.copy_on_exchange);
2075 assert!(!cfg.fail_fast);
2076 assert!(!cfg.any_order);
2077 }
2078
2079 use futures::FutureExt;
2084
2085 #[tokio::test]
2086 async fn test_trigger_fail_fast_rejects_subsequent_producer() {
2087 use camel_component_api::CamelError;
2088 use std::panic::AssertUnwindSafe;
2089 let config = MockConfig {
2090 fail_fast: true,
2091 ..Default::default()
2092 };
2093 let component = MockComponent::with_config(config);
2094 let endpoint = component
2095 .create_endpoint("mock:test", &NoOpComponentContext)
2096 .unwrap();
2097 let inner = component.get_endpoint("test").unwrap();
2098
2099 inner.trigger_fail_fast(CamelError::ProcessorError("boom".to_string()));
2100
2101 let ctx = test_producer_ctx();
2102 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2103 assert!(producer.ready().await.is_err());
2105 let result = AssertUnwindSafe(producer.call(Exchange::default()))
2107 .catch_unwind()
2108 .await
2109 .expect("call should not panic");
2110 match result {
2111 Err(CamelError::ProcessorError(msg)) => {
2112 assert!(
2113 msg.contains("fail-fast mode"),
2114 "message should contain 'fail-fast mode', got: {msg}"
2115 );
2116 assert!(
2117 !msg.contains("boom"),
2118 "supplied error must NOT be in fixed message, got: {msg}"
2119 );
2120 }
2121 other => panic!("expected ProcessorError, got {other:?}"),
2122 }
2123 }
2124
2125 #[tokio::test]
2126 async fn test_trigger_fail_fast_noop_when_fail_fast_false() {
2127 use camel_component_api::CamelError;
2128 use std::panic::AssertUnwindSafe;
2129 let config = MockConfig {
2130 fail_fast: false,
2131 ..Default::default()
2132 };
2133 let component = MockComponent::with_config(config);
2134 let endpoint = component
2135 .create_endpoint("mock:test", &NoOpComponentContext)
2136 .unwrap();
2137 let inner = component.get_endpoint("test").unwrap();
2138
2139 inner.trigger_fail_fast(CamelError::ProcessorError("boom".to_string()));
2140
2141 let ctx = test_producer_ctx();
2142 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2143 let result = AssertUnwindSafe(producer.call(Exchange::default()))
2144 .catch_unwind()
2145 .await
2146 .expect("call should not panic");
2147 assert!(
2148 result.is_ok(),
2149 "fail_fast=false must let the call through even with stored error"
2150 );
2151 }
2152
2153 #[tokio::test]
2154 async fn test_reset_clears_trigger_fail_fast() {
2155 use camel_component_api::CamelError;
2156 use std::panic::AssertUnwindSafe;
2157 let config = MockConfig {
2158 fail_fast: true,
2159 ..Default::default()
2160 };
2161 let component = MockComponent::with_config(config);
2162 let endpoint = component
2163 .create_endpoint("mock:test", &NoOpComponentContext)
2164 .unwrap();
2165 let inner = component.get_endpoint("test").unwrap();
2166
2167 inner.trigger_fail_fast(CamelError::ProcessorError("boom".to_string()));
2168 assert!(inner.fail_fast_error().is_some());
2169 inner.reset().await;
2170 assert!(inner.fail_fast_error().is_none());
2171
2172 let ctx = test_producer_ctx();
2174 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2175 let result = AssertUnwindSafe(producer.call(Exchange::default()))
2176 .catch_unwind()
2177 .await
2178 .expect("call should not panic");
2179 assert!(result.is_ok());
2180 }
2181
2182 #[tokio::test]
2183 async fn test_assert_satisfied_body_count_mismatch_sets_fail_fast() {
2184 use std::panic::AssertUnwindSafe;
2185 let config = MockConfig {
2186 fail_fast: true,
2187 ..Default::default()
2188 };
2189 let component = MockComponent::with_config(config);
2190 let endpoint = component
2191 .create_endpoint("mock:test", &NoOpComponentContext)
2192 .unwrap();
2193 let inner = component.get_endpoint("test").unwrap();
2194
2195 inner.expect_body(camel_component_api::Body::Text("a".to_string()));
2196 inner.expect_body(camel_component_api::Body::Text("b".to_string()));
2197
2198 let ctx = test_producer_ctx();
2200 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2201 producer
2202 .call(Exchange::new(Message::new("a")))
2203 .await
2204 .unwrap();
2205
2206 let panic_result = AssertUnwindSafe(inner.assert_satisfied())
2208 .catch_unwind()
2209 .await;
2210 assert!(
2211 panic_result.is_err(),
2212 "expected panic from assert_satisfied"
2213 );
2214 assert!(
2215 inner.fail_fast_error().is_some(),
2216 "fail_fast_error must be set when fail_fast=true and assertion panics"
2217 );
2218 }
2219
2220 #[tokio::test]
2221 async fn test_assert_satisfied_body_mismatch_sets_fail_fast() {
2222 use std::panic::AssertUnwindSafe;
2223 let config = MockConfig {
2224 fail_fast: true,
2225 ..Default::default()
2226 };
2227 let component = MockComponent::with_config(config);
2228 let endpoint = component
2229 .create_endpoint("mock:test", &NoOpComponentContext)
2230 .unwrap();
2231 let inner = component.get_endpoint("test").unwrap();
2232
2233 inner.expect_body(camel_component_api::Body::Text("expected".to_string()));
2234
2235 let ctx = test_producer_ctx();
2236 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2237 producer
2238 .call(Exchange::new(Message::new("actual")))
2239 .await
2240 .unwrap();
2241
2242 let panic_result = AssertUnwindSafe(inner.assert_satisfied())
2243 .catch_unwind()
2244 .await;
2245 assert!(
2246 panic_result.is_err(),
2247 "expected panic from assert_satisfied"
2248 );
2249 assert!(
2250 inner.fail_fast_error().is_some(),
2251 "fail_fast_error must be set on body mismatch when fail_fast=true"
2252 );
2253 }
2254
2255 #[tokio::test]
2256 async fn test_assert_satisfied_no_set_error_when_fail_fast_false() {
2257 use std::panic::AssertUnwindSafe;
2258 let config = MockConfig {
2259 fail_fast: false,
2260 ..Default::default()
2261 };
2262 let component = MockComponent::with_config(config);
2263 let endpoint = component
2264 .create_endpoint("mock:test", &NoOpComponentContext)
2265 .unwrap();
2266 let inner = component.get_endpoint("test").unwrap();
2267
2268 inner.expect_body(camel_component_api::Body::Text("a".to_string()));
2269 inner.expect_body(camel_component_api::Body::Text("b".to_string()));
2270
2271 let ctx = test_producer_ctx();
2273 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2274 producer
2275 .call(Exchange::new(Message::new("a")))
2276 .await
2277 .unwrap();
2278
2279 let panic_result = AssertUnwindSafe(inner.assert_satisfied())
2280 .catch_unwind()
2281 .await;
2282 assert!(
2283 panic_result.is_err(),
2284 "expected panic from assert_satisfied"
2285 );
2286 assert!(
2287 inner.fail_fast_error().is_none(),
2288 "fail_fast_error must remain None when fail_fast=false"
2289 );
2290 }
2291
2292 #[tokio::test]
2293 async fn test_assert_satisfied_any_order_body_mismatch_sets_fail_fast() {
2294 use std::panic::AssertUnwindSafe;
2295 let config = MockConfig {
2296 fail_fast: true,
2297 any_order: true,
2298 ..Default::default()
2299 };
2300 let component = MockComponent::with_config(config);
2301 let endpoint = component
2302 .create_endpoint("mock:test", &NoOpComponentContext)
2303 .unwrap();
2304 let inner = component.get_endpoint("test").unwrap();
2305
2306 inner.expect_body(camel_component_api::Body::Text("a".to_string()));
2307 inner.expect_body(camel_component_api::Body::Text("b".to_string()));
2308
2309 let ctx = test_producer_ctx();
2311 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2312 producer
2313 .call(Exchange::new(Message::new("a")))
2314 .await
2315 .unwrap();
2316 producer
2317 .call(Exchange::new(Message::new("c")))
2318 .await
2319 .unwrap();
2320
2321 let panic_result = AssertUnwindSafe(inner.assert_satisfied())
2322 .catch_unwind()
2323 .await;
2324 assert!(
2325 panic_result.is_err(),
2326 "expected panic from assert_satisfied (any-order body not found)"
2327 );
2328 assert!(
2329 inner.fail_fast_error().is_some(),
2330 "fail_fast_error must be set when fail_fast=true and any-order body is missing"
2331 );
2332 }
2333
2334 #[tokio::test]
2335 async fn test_assert_satisfied_header_missing_sets_fail_fast() {
2336 use std::panic::AssertUnwindSafe;
2337 let config = MockConfig {
2338 fail_fast: true,
2339 ..Default::default()
2340 };
2341 let component = MockComponent::with_config(config);
2342 let endpoint = component
2343 .create_endpoint("mock:test", &NoOpComponentContext)
2344 .unwrap();
2345 let inner = component.get_endpoint("test").unwrap();
2346
2347 inner.expect_header("x-missing", serde_json::json!("value"));
2348
2349 let ctx = test_producer_ctx();
2351 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
2352 producer
2353 .call(Exchange::new(Message::new("body")))
2354 .await
2355 .unwrap();
2356
2357 let panic_result = AssertUnwindSafe(inner.assert_satisfied())
2358 .catch_unwind()
2359 .await;
2360 assert!(
2361 panic_result.is_err(),
2362 "expected panic from assert_satisfied (header missing)"
2363 );
2364 assert!(
2365 inner.fail_fast_error().is_some(),
2366 "fail_fast_error must be set when fail_fast=true and expected header is missing"
2367 );
2368 }
2369}