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::{ComponentCapabilities, ComponentMetadata};
37use camel_component_api::parse_uri;
38use camel_component_api::{BoxProcessor, CamelError, Exchange};
39use camel_component_api::{Component, Consumer, Endpoint, ProducerContext, RuntimeObservability};
40use tracing::debug;
41
42const DEFAULT_MAX_RETAINED: usize = 10_000;
44
45#[derive(Clone, Debug)]
68pub struct MockConfig {
69 pub max_retained: usize,
72 pub copy_on_exchange: bool,
76 pub fail_fast: bool,
79 pub assert_period_ms: u64,
82 pub any_order: bool,
85}
86
87impl Default for MockConfig {
88 fn default() -> Self {
89 Self {
90 max_retained: DEFAULT_MAX_RETAINED,
91 copy_on_exchange: false,
92 fail_fast: false,
93 assert_period_ms: 0,
94 any_order: false,
95 }
96 }
97}
98
99impl MockConfig {
100 pub fn new(max_retained: usize) -> Self {
102 Self {
103 max_retained,
104 ..Self::default()
105 }
106 }
107}
108
109pub struct MockExpectations {
119 expected_bodies: Vec<camel_component_api::Body>,
120 expected_headers: Vec<(String, serde_json::Value)>,
121 expected_header_regexes: Vec<(String, String)>,
122}
123
124impl Default for MockExpectations {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl MockExpectations {
131 pub fn new() -> Self {
133 Self {
134 expected_bodies: Vec::new(),
135 expected_headers: Vec::new(),
136 expected_header_regexes: Vec::new(),
137 }
138 }
139
140 pub fn push_body(&mut self, body: camel_component_api::Body) {
142 self.expected_bodies.push(body);
143 }
144
145 pub fn push_header(&mut self, key: String, value: serde_json::Value) {
147 self.expected_headers.push((key, value));
148 }
149
150 pub fn push_header_regex(&mut self, key: String, pattern: String) {
152 self.expected_header_regexes.push((key, pattern));
153 }
154}
155
156#[derive(Clone)]
171pub struct MockComponent {
172 registry: Arc<std::sync::Mutex<HashMap<String, Arc<MockEndpointInner>>>>,
173 config: MockConfig,
174}
175
176impl MockComponent {
177 pub fn new() -> Self {
178 Self::with_config(MockConfig::default())
179 }
180
181 pub fn with_config(config: MockConfig) -> Self {
183 Self {
184 registry: Arc::new(std::sync::Mutex::new(HashMap::new())),
185 config,
186 }
187 }
188
189 pub fn get_endpoint(&self, name: &str) -> Option<Arc<MockEndpointInner>> {
193 let registry = self
194 .registry
195 .lock()
196 .expect("mutex poisoned: another thread panicked while holding this lock"); registry.get(name).cloned()
198 }
199}
200
201impl Default for MockComponent {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207impl Component for MockComponent {
208 fn scheme(&self) -> &str {
209 "mock"
210 }
211
212 fn metadata(&self) -> ComponentMetadata {
213 ComponentMetadata {
214 scheme: "mock".to_string(),
215 version: env!("CARGO_PKG_VERSION").to_string(),
216 description: "Records exchanges for test assertions".to_string(),
217 uri_syntax: "mock:name".to_string(),
218 capabilities: ComponentCapabilities {
219 supports_producer: true,
220 ..Default::default()
221 },
222 ..ComponentMetadata::minimal("mock")
223 }
224 }
225
226 fn create_endpoint(
227 &self,
228 uri: &str,
229 _ctx: &dyn camel_component_api::ComponentContext,
230 ) -> Result<Box<dyn Endpoint>, CamelError> {
231 let parts = parse_uri(uri)?;
232 if parts.scheme != "mock" {
233 return Err(CamelError::InvalidUri(format!(
234 "expected scheme 'mock', got '{}'",
235 parts.scheme
236 )));
237 }
238
239 let name = parts.path;
240 if name.is_empty() {
241 return Err(CamelError::InvalidUri(
242 "mock endpoint name must be non-empty (use 'mock:<name>')".to_string(),
243 ));
244 }
245 let mut registry = self.registry.lock().map_err(|e| {
246 CamelError::EndpointCreationFailed(format!("mock registry lock poisoned: {e}"))
247 })?;
248 let max_retained = self.config.max_retained;
249 let copy_on_exchange = self.config.copy_on_exchange;
250 let fail_fast = self.config.fail_fast;
251 let assert_period_ms = self.config.assert_period_ms;
252 let any_order = self.config.any_order;
253 let inner = registry
254 .entry(name.clone())
255 .or_insert_with(|| {
256 Arc::new(MockEndpointInner {
257 uri: uri.to_string(),
258 name,
259 received: Arc::new(Mutex::new(VecDeque::new())),
260 notify: Arc::new(Notify::new()),
261 max_retained,
262 copy_on_exchange,
263 fail_fast,
264 fail_fast_error: Arc::new(std::sync::Mutex::new(None)),
265 assert_period_ms,
266 any_order,
267 expectations: Arc::new(std::sync::Mutex::new(MockExpectations::new())),
268 })
269 })
270 .clone();
271
272 debug!(endpoint_name = %inner.name, "mock endpoint created");
273 Ok(Box::new(MockEndpoint(inner)))
274 }
275}
276
277pub struct MockEndpoint(Arc<MockEndpointInner>);
287
288pub struct MockEndpointInner {
294 uri: String,
295 pub name: String,
296 received: Arc<Mutex<VecDeque<Exchange>>>,
297 notify: Arc<Notify>,
298 max_retained: usize,
299 copy_on_exchange: bool,
300 fail_fast: bool,
301 fail_fast_error: Arc<std::sync::Mutex<Option<CamelError>>>,
302 assert_period_ms: u64,
303 any_order: bool,
304 expectations: Arc<std::sync::Mutex<MockExpectations>>,
305}
306
307impl MockEndpointInner {
308 pub async fn get_received_exchanges(&self) -> Vec<Exchange> {
310 self.received.lock().await.iter().cloned().collect()
311 }
312
313 pub async fn received_count(&self) -> usize {
315 self.received.lock().await.len()
316 }
317
318 pub async fn reset(&self) {
322 self.received.lock().await.clear();
323 if let Ok(mut guard) = self.fail_fast_error.lock() {
324 *guard = None;
325 }
326 }
327
328 pub async fn assert_exchange_count(&self, expected: usize) {
334 let actual = self.received.lock().await.len();
335 assert_eq!(
336 actual, expected,
337 "MockEndpoint expected {expected} exchanges, got {actual}"
338 );
339 }
340
341 pub async fn await_exchanges(&self, count: usize, timeout: std::time::Duration) {
350 let deadline = tokio::time::Instant::now() + timeout;
351 loop {
352 {
353 let received = self.received.lock().await;
354 if received.len() >= count {
355 return;
356 }
357 }
358 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
359 if remaining.is_zero() {
360 let got = self.received.lock().await.len();
363 if got >= count {
364 return;
365 }
366 panic!(
367 "MockEndpoint '{}': timed out waiting for {} exchanges (got {} after {:?})",
368 self.name, count, got, timeout
369 );
370 }
371 tokio::select! {
372 _ = self.notify.notified() => {}
373 _ = tokio::time::sleep(remaining) => {}
374 }
375 }
376 }
377
378 pub async fn await_exchanges_with_timeout(&self, count: usize, fallback: std::time::Duration) {
383 let duration = if self.assert_period_ms > 0 {
384 std::time::Duration::from_millis(self.assert_period_ms)
385 } else {
386 fallback
387 };
388 self.await_exchanges(count, duration).await;
389 }
390
391 pub fn exchange(&self, idx: usize) -> ExchangeAssert {
405 let received = tokio::task::block_in_place(|| self.received.blocking_lock());
406 if idx >= received.len() {
407 panic!(
408 "MockEndpoint '{}': exchange index {} out of bounds (got {} exchanges)",
409 self.name,
410 idx,
411 received.len()
412 );
413 }
414 ExchangeAssert {
415 exchange: received[idx].clone(),
416 idx,
417 endpoint_name: self.name.clone(),
418 }
419 }
420
421 pub fn expect_body(&self, body: camel_component_api::Body) {
423 if let Ok(mut guard) = self.expectations.lock() {
424 guard.push_body(body);
425 }
426 }
427
428 pub fn expect_header(&self, key: &str, value: impl Into<serde_json::Value>) {
430 if let Ok(mut guard) = self.expectations.lock() {
431 guard.push_header(key.to_string(), value.into());
432 }
433 }
434
435 pub fn expect_header_regex(&self, key: &str, pattern: &str) {
440 if let Ok(mut guard) = self.expectations.lock() {
441 guard.push_header_regex(key.to_string(), pattern.to_string());
442 }
443 }
444
445 pub async fn assert_satisfied(&self) {
453 let received = self.get_received_exchanges().await;
454
455 {
457 let guard = self
458 .expectations
459 .lock()
460 .expect("expectations lock poisoned"); if !guard.expected_bodies.is_empty() {
462 let received_bodies: Vec<_> = received.iter().map(|e| &e.input.body).collect();
463 if guard.expected_bodies.len() != received_bodies.len() {
464 panic!(
465 "MockEndpoint '{}': expected {} bodies, got {}",
466 self.name,
467 guard.expected_bodies.len(),
468 received_bodies.len()
469 );
470 }
471 if self.any_order {
472 let mut unmatched: Vec<_> = received_bodies.iter().collect();
474 for expected in &guard.expected_bodies {
475 let idx = unmatched
476 .iter()
477 .position(|actual| body_eq(expected, actual));
478 match idx {
479 Some(i) => {
480 unmatched.remove(i);
481 }
482 None => panic!(
483 "MockEndpoint '{}': expected body {:?} not found in received exchanges (anyOrder mode)",
484 self.name, expected
485 ),
486 }
487 }
488 } else {
489 for (i, expected) in guard.expected_bodies.iter().enumerate() {
490 if !body_eq(expected, received_bodies[i]) {
491 panic!(
492 "MockEndpoint '{}': body[{}] expected {:?}, got {:?}",
493 self.name, i, expected, received_bodies[i]
494 );
495 }
496 }
497 }
498 }
499
500 for (key, value) in &guard.expected_headers {
502 let found = received
503 .iter()
504 .any(|ex| ex.input.headers.get(key).is_some_and(|v| v == value));
505 if !found {
506 panic!(
507 "MockEndpoint '{}': expected header '{}' = {} not found in any received exchange",
508 self.name, key, value
509 );
510 }
511 }
512
513 for (key, pattern) in &guard.expected_header_regexes {
515 let re = regex::Regex::new(pattern).unwrap_or_else(|e| {
516 panic!(
517 "MockEndpoint '{}': invalid regex pattern {:?}: {e}",
518 self.name, pattern
519 )
520 });
521 let found = received.iter().any(|ex| {
522 ex.input.headers.get(key).is_some_and(|v| {
523 let s = match v {
524 serde_json::Value::String(s) => s.clone(),
525 other => other.to_string(),
526 };
527 re.is_match(&s)
528 })
529 });
530 if !found {
531 panic!(
532 "MockEndpoint '{}': no received exchange has header '{}' matching regex {:?}",
533 self.name, key, pattern
534 );
535 }
536 }
537 }
538 }
539
540 pub fn fail_fast_error(&self) -> Option<CamelError> {
542 self.fail_fast_error.lock().ok().and_then(|g| g.clone())
543 }
544}
545
546fn body_eq(a: &camel_component_api::Body, b: &camel_component_api::Body) -> bool {
548 match (a, b) {
549 (camel_component_api::Body::Empty, camel_component_api::Body::Empty) => true,
550 (camel_component_api::Body::Text(a), camel_component_api::Body::Text(b)) => a == b,
551 (camel_component_api::Body::Json(a), camel_component_api::Body::Json(b)) => a == b,
552 (camel_component_api::Body::Xml(a), camel_component_api::Body::Xml(b)) => a == b,
553 (camel_component_api::Body::Bytes(a), camel_component_api::Body::Bytes(b)) => a == b,
554 _ => false,
555 }
556}
557
558impl Endpoint for MockEndpoint {
559 fn uri(&self) -> &str {
560 &self.0.uri
561 }
562
563 fn create_consumer(
564 &self,
565 _rt: Arc<dyn RuntimeObservability>,
566 ) -> Result<Box<dyn Consumer>, CamelError> {
567 Err(CamelError::EndpointCreationFailed(
568 "mock endpoint does not support consumers (it is a sink)".to_string(),
569 ))
570 }
571
572 fn create_producer(
573 &self,
574 _rt: Arc<dyn RuntimeObservability>,
575 _ctx: &ProducerContext,
576 ) -> Result<BoxProcessor, CamelError> {
577 Ok(BoxProcessor::new(MockProducer {
578 name: self.0.name.clone(),
579 received: Arc::clone(&self.0.received),
580 notify: Arc::clone(&self.0.notify),
581 max_retained: self.0.max_retained,
582 copy_on_exchange: self.0.copy_on_exchange,
583 fail_fast: self.0.fail_fast,
584 fail_fast_error: Arc::clone(&self.0.fail_fast_error),
585 }))
586 }
587}
588
589#[derive(Clone)]
595struct MockProducer {
596 name: String,
597 received: Arc<Mutex<VecDeque<Exchange>>>,
598 notify: Arc<Notify>,
599 max_retained: usize,
600 copy_on_exchange: bool,
601 fail_fast: bool,
602 fail_fast_error: Arc<std::sync::Mutex<Option<CamelError>>>,
603}
604
605impl Service<Exchange> for MockProducer {
606 type Response = Exchange;
607 type Error = CamelError;
608 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
609
610 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
611 if self.fail_fast
613 && let Ok(guard) = self.fail_fast_error.lock()
614 && guard.is_some()
615 {
616 return Poll::Ready(Err(CamelError::ProcessorError(
617 "mock endpoint in fail-fast mode: a previous exchange caused an error".to_string(),
618 )));
619 }
620 Poll::Ready(Ok(()))
621 }
622
623 fn call(&mut self, exchange: Exchange) -> Self::Future {
624 let name = self.name.clone();
625 let received = Arc::clone(&self.received);
626 let notify = Arc::clone(&self.notify);
627 let max_retained = self.max_retained;
628 let copy_on_exchange = self.copy_on_exchange;
629 let fail_fast = self.fail_fast;
630 let fail_fast_error = Arc::clone(&self.fail_fast_error);
631 Box::pin(async move {
632 if fail_fast
634 && let Ok(guard) = fail_fast_error.lock()
635 && guard.is_some()
636 {
637 return Err(CamelError::ProcessorError(
638 "mock endpoint in fail-fast mode: a previous exchange caused an error"
639 .to_string(),
640 ));
641 }
642
643 let correlation_id = exchange
644 .input
645 .headers
646 .get("CamelCorrelationId")
647 .and_then(|v| v.as_str())
648 .map(|s| s.to_string());
649
650 let exchange_to_store = if copy_on_exchange {
651 let mut cloned = exchange.clone();
652 cloned.input.body = clone_body(&exchange.input.body);
654 cloned
655 } else {
656 exchange.clone()
657 };
658
659 let mut guard = received.lock().await;
660 if guard.len() >= max_retained {
661 tracing::warn!(
662 endpoint_name = %name,
663 max = max_retained,
664 "max retained exchanges reached, dropping oldest"
665 );
666 guard.pop_front();
667 }
668 guard.push_back(exchange_to_store);
669 let count = guard.len();
670 drop(guard);
671
672 debug!(
673 endpoint_name = %name,
674 count = %count,
675 correlation_id = correlation_id.as_deref().unwrap_or("none"),
676 "exchange recorded on mock"
677 );
678 notify.notify_waiters();
679
680 Ok(exchange)
681 })
682 }
683}
684
685fn clone_body(body: &camel_component_api::Body) -> camel_component_api::Body {
687 match body {
688 camel_component_api::Body::Empty => camel_component_api::Body::Empty,
689 camel_component_api::Body::Text(s) => camel_component_api::Body::Text(s.clone()),
690 camel_component_api::Body::Json(v) => camel_component_api::Body::Json(v.clone()),
691 camel_component_api::Body::Xml(s) => camel_component_api::Body::Xml(s.clone()),
692 camel_component_api::Body::Bytes(b) => camel_component_api::Body::Bytes(b.clone()),
693 camel_component_api::Body::Stream(_) => {
694 camel_component_api::Body::Empty
696 }
697 }
698}
699
700pub struct ExchangeAssert {
712 exchange: Exchange,
713 idx: usize,
714 endpoint_name: String,
715}
716
717impl ExchangeAssert {
718 fn location(&self) -> String {
719 format!(
720 "MockEndpoint '{}' exchange[{}]",
721 self.endpoint_name, self.idx
722 )
723 }
724
725 pub fn assert_body_text(self, expected: &str) -> Self {
727 match self.exchange.input.body.as_text() {
728 Some(actual) if actual == expected => {}
729 Some(actual) => panic!(
730 "{}: expected body text {:?}, got {:?}",
731 self.location(),
732 expected,
733 actual
734 ),
735 None => panic!(
736 "{}: expected body text {:?}, but body is not Body::Text (got {:?})",
737 self.location(),
738 expected,
739 self.exchange.input.body
740 ),
741 }
742 self
743 }
744
745 pub fn assert_body_json(self, expected: serde_json::Value) -> Self {
747 match &self.exchange.input.body {
748 camel_component_api::Body::Json(actual) if *actual == expected => {}
749 camel_component_api::Body::Json(actual) => panic!(
750 "{}: expected body JSON {}, got {}",
751 self.location(),
752 expected,
753 actual
754 ),
755 other => panic!(
756 "{}: expected body JSON {}, but body is not Body::Json (got {:?})",
757 self.location(),
758 expected,
759 other
760 ),
761 }
762 self
763 }
764
765 pub fn assert_body_bytes(self, expected: &[u8]) -> Self {
767 match &self.exchange.input.body {
768 camel_component_api::Body::Bytes(actual) if actual.as_ref() == expected => {}
769 camel_component_api::Body::Bytes(actual) => panic!(
770 "{}: expected body bytes {:?}, got {:?}",
771 self.location(),
772 expected,
773 actual
774 ),
775 other => panic!(
776 "{}: expected body bytes {:?}, but body is not Body::Bytes (got {:?})",
777 self.location(),
778 expected,
779 other
780 ),
781 }
782 self
783 }
784
785 pub fn assert_header(self, key: &str, expected: serde_json::Value) -> Self {
791 match self.exchange.input.headers.get(key) {
792 Some(actual) if *actual == expected => {}
793 Some(actual) => panic!(
794 "{}: expected header {:?} = {}, got {}",
795 self.location(),
796 key,
797 expected,
798 actual
799 ),
800 None => panic!(
801 "{}: expected header {:?} = {}, but header is absent",
802 self.location(),
803 key,
804 expected
805 ),
806 }
807 self
808 }
809
810 pub fn assert_header_exists(self, key: &str) -> Self {
816 if !self.exchange.input.headers.contains_key(key) {
817 panic!(
818 "{}: expected header {:?} to be present, but it was absent",
819 self.location(),
820 key
821 );
822 }
823 self
824 }
825
826 pub fn assert_has_error(self) -> Self {
832 if self.exchange.error.is_none() {
833 panic!(
834 "{}: expected exchange to have an error, but error is None",
835 self.location()
836 );
837 }
838 self
839 }
840
841 pub fn assert_no_error(self) -> Self {
847 if let Some(ref err) = self.exchange.error {
848 panic!(
849 "{}: expected exchange to have no error, but got: {}",
850 self.location(),
851 err
852 );
853 }
854 self
855 }
856}
857
858#[cfg(test)]
863mod tests {
864 use camel_component_api::test_support::PanicRuntimeObservability;
865 fn rt() -> std::sync::Arc<dyn camel_component_api::RuntimeObservability> {
866 std::sync::Arc::new(PanicRuntimeObservability)
867 }
868
869 use super::*;
870 use camel_component_api::Message;
871 use camel_component_api::NoOpComponentContext;
872 use tower::ServiceExt;
873
874 fn test_producer_ctx() -> ProducerContext {
875 ProducerContext::new()
876 }
877
878 #[test]
879 fn test_mock_component_scheme() {
880 let component = MockComponent::new();
881 assert_eq!(component.scheme(), "mock");
882 }
883
884 #[test]
885 fn test_mock_component_default() {
886 let component = MockComponent::default();
887 assert_eq!(component.scheme(), "mock");
888 assert!(component.get_endpoint("missing").is_none());
889 }
890
891 #[test]
892 fn test_mock_creates_endpoint() {
893 let component = MockComponent::new();
894 let endpoint = component.create_endpoint("mock:result", &NoOpComponentContext);
895 assert!(endpoint.is_ok());
896 }
897
898 #[test]
899 fn test_mock_wrong_scheme() {
900 let component = MockComponent::new();
901 let result = component.create_endpoint("timer:tick", &NoOpComponentContext);
902 assert!(result.is_err());
903 }
904
905 #[test]
906 fn test_empty_mock_endpoint_name_rejected() {
907 let component = MockComponent::new();
908 let result = component.create_endpoint("mock:", &NoOpComponentContext);
909 assert!(result.is_err(), "empty mock name should be rejected");
910 }
911
912 #[test]
913 fn test_valid_mock_endpoint_name_accepted() {
914 let component = MockComponent::new();
915 let result = component.create_endpoint("mock:result", &NoOpComponentContext);
916 assert!(result.is_ok());
917 }
918
919 #[test]
920 fn test_mock_endpoint_no_consumer() {
921 let component = MockComponent::new();
922 let endpoint = component
923 .create_endpoint("mock:result", &NoOpComponentContext)
924 .unwrap();
925 assert!(endpoint.create_consumer(rt()).is_err());
926 }
927
928 #[test]
929 fn test_mock_endpoint_creates_producer() {
930 let ctx = test_producer_ctx();
931 let component = MockComponent::new();
932 let endpoint = component
933 .create_endpoint("mock:result", &NoOpComponentContext)
934 .unwrap();
935 assert!(endpoint.create_producer(rt(), &ctx).is_ok());
936 }
937
938 #[test]
939 fn test_mock_endpoint_uri() {
940 let component = MockComponent::new();
941 let endpoint = component
942 .create_endpoint("mock:uri-check", &NoOpComponentContext)
943 .unwrap();
944 assert_eq!(endpoint.uri(), "mock:uri-check");
945 }
946
947 #[test]
948 fn test_mock_get_endpoint_returns_same_inner_for_same_name() {
949 let component = MockComponent::new();
950 let _ = component
951 .create_endpoint("mock:shared-inner", &NoOpComponentContext)
952 .unwrap();
953 let _ = component
954 .create_endpoint("mock:shared-inner", &NoOpComponentContext)
955 .unwrap();
956
957 let first = component.get_endpoint("shared-inner").unwrap();
958 let second = component.get_endpoint("shared-inner").unwrap();
959 assert!(Arc::ptr_eq(&first, &second));
960 }
961
962 #[tokio::test]
963 async fn test_mock_producer_records_exchange() {
964 let ctx = test_producer_ctx();
965 let component = MockComponent::new();
966 let endpoint = component
967 .create_endpoint("mock:test", &NoOpComponentContext)
968 .unwrap();
969
970 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
971
972 let ex1 = Exchange::new(Message::new("first"));
973 let ex2 = Exchange::new(Message::new("second"));
974
975 producer.call(ex1).await.unwrap();
976 producer.call(ex2).await.unwrap();
977
978 let inner = component.get_endpoint("test").unwrap();
979 inner.assert_exchange_count(2).await;
980
981 let received = inner.get_received_exchanges().await;
982 assert_eq!(received[0].input.body.as_text(), Some("first"));
983 assert_eq!(received[1].input.body.as_text(), Some("second"));
984 }
985
986 #[tokio::test]
987 async fn test_mock_producer_passes_through_exchange() {
988 let ctx = test_producer_ctx();
989 let component = MockComponent::new();
990 let endpoint = component
991 .create_endpoint("mock:passthrough", &NoOpComponentContext)
992 .unwrap();
993
994 let producer = endpoint.create_producer(rt(), &ctx).unwrap();
995 let exchange = Exchange::new(Message::new("hello"));
996 let result = producer.oneshot(exchange).await.unwrap();
997
998 assert_eq!(result.input.body.as_text(), Some("hello"));
1000 }
1001
1002 #[tokio::test]
1003 async fn test_mock_assert_count_passes() {
1004 let component = MockComponent::new();
1005 let endpoint = component
1006 .create_endpoint("mock:count", &NoOpComponentContext)
1007 .unwrap();
1008 let inner = component.get_endpoint("count").unwrap();
1009
1010 inner.assert_exchange_count(0).await;
1011
1012 let ctx = test_producer_ctx();
1013 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1014 producer
1015 .call(Exchange::new(Message::new("one")))
1016 .await
1017 .unwrap();
1018
1019 inner.assert_exchange_count(1).await;
1020 }
1021
1022 #[tokio::test]
1023 #[should_panic(expected = "MockEndpoint expected 5 exchanges, got 0")]
1024 async fn test_mock_assert_count_fails() {
1025 let component = MockComponent::new();
1026 let _endpoint = component
1029 .create_endpoint("mock:fail", &NoOpComponentContext)
1030 .unwrap();
1031 let inner = component.get_endpoint("fail").unwrap();
1032
1033 inner.assert_exchange_count(5).await;
1034 }
1035
1036 #[tokio::test]
1037 async fn test_mock_component_shared_registry() {
1038 let component = MockComponent::new();
1039 let ep1 = component
1040 .create_endpoint("mock:shared", &NoOpComponentContext)
1041 .unwrap();
1042 let ep2 = component
1043 .create_endpoint("mock:shared", &NoOpComponentContext)
1044 .unwrap();
1045
1046 let ctx = test_producer_ctx();
1048 let mut p1 = ep1.create_producer(rt(), &ctx).unwrap();
1049 p1.call(Exchange::new(Message::new("from-ep1")))
1050 .await
1051 .unwrap();
1052
1053 let mut p2 = ep2.create_producer(rt(), &ctx).unwrap();
1055 p2.call(Exchange::new(Message::new("from-ep2")))
1056 .await
1057 .unwrap();
1058
1059 let inner = component.get_endpoint("shared").unwrap();
1061 inner.assert_exchange_count(2).await;
1062
1063 let received = inner.get_received_exchanges().await;
1064 assert_eq!(received[0].input.body.as_text(), Some("from-ep1"));
1065 assert_eq!(received[1].input.body.as_text(), Some("from-ep2"));
1066 }
1067
1068 #[tokio::test]
1069 async fn await_exchanges_resolves_immediately() {
1070 let ctx = test_producer_ctx();
1072 let component = MockComponent::new();
1073 let endpoint = component
1074 .create_endpoint("mock:immediate", &NoOpComponentContext)
1075 .unwrap();
1076 let inner = component.get_endpoint("immediate").unwrap();
1077
1078 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1079 producer
1080 .call(Exchange::new(Message::new("a")))
1081 .await
1082 .unwrap();
1083 producer
1084 .call(Exchange::new(Message::new("b")))
1085 .await
1086 .unwrap();
1087
1088 inner
1090 .await_exchanges(2, std::time::Duration::from_millis(100))
1091 .await;
1092 }
1093
1094 #[tokio::test]
1095 async fn await_exchanges_waits_then_resolves() {
1096 let ctx = test_producer_ctx();
1098 let component = MockComponent::new();
1099 let endpoint = component
1100 .create_endpoint("mock:waiter", &NoOpComponentContext)
1101 .unwrap();
1102 let inner = component.get_endpoint("waiter").unwrap();
1103
1104 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1106 tokio::spawn(async move {
1107 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1108 producer
1109 .call(Exchange::new(Message::new("delayed")))
1110 .await
1111 .unwrap();
1112 });
1113
1114 inner
1116 .await_exchanges(1, std::time::Duration::from_millis(500))
1117 .await;
1118
1119 let received = inner.get_received_exchanges().await;
1120 assert_eq!(received.len(), 1);
1121 assert_eq!(received[0].input.body.as_text(), Some("delayed"));
1122 }
1123
1124 #[tokio::test]
1125 #[should_panic(expected = "timed out waiting for 5 exchanges")]
1126 async fn await_exchanges_times_out() {
1127 let component = MockComponent::new();
1128 let _endpoint = component
1129 .create_endpoint("mock:timeout", &NoOpComponentContext)
1130 .unwrap();
1131 let inner = component.get_endpoint("timeout").unwrap();
1132
1133 inner
1135 .await_exchanges(5, std::time::Duration::from_millis(50))
1136 .await;
1137 }
1138
1139 #[tokio::test(flavor = "multi_thread")]
1140 async fn exchange_idx_returns_assert() {
1141 let ctx = test_producer_ctx();
1142 let component = MockComponent::new();
1143 let endpoint = component
1144 .create_endpoint("mock:assert-idx", &NoOpComponentContext)
1145 .unwrap();
1146 let inner = component.get_endpoint("assert-idx").unwrap();
1147
1148 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1149 producer
1150 .call(Exchange::new(Message::new("hello")))
1151 .await
1152 .unwrap();
1153
1154 inner
1155 .await_exchanges(1, std::time::Duration::from_millis(500))
1156 .await;
1157 let _assert = inner.exchange(0);
1159 }
1160
1161 #[tokio::test(flavor = "multi_thread")]
1162 #[should_panic(expected = "exchange index 5 out of bounds")]
1163 async fn exchange_idx_out_of_bounds() {
1164 let ctx = test_producer_ctx();
1165 let component = MockComponent::new();
1166 let endpoint = component
1167 .create_endpoint("mock:oob", &NoOpComponentContext)
1168 .unwrap();
1169 let inner = component.get_endpoint("oob").unwrap();
1170
1171 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1172 producer
1173 .call(Exchange::new(Message::new("only-one")))
1174 .await
1175 .unwrap();
1176
1177 inner
1178 .await_exchanges(1, std::time::Duration::from_millis(500))
1179 .await;
1180 let _assert = inner.exchange(5);
1182 }
1183
1184 #[tokio::test(flavor = "multi_thread")]
1185 async fn assert_body_text_pass() {
1186 let ctx = test_producer_ctx();
1187 let component = MockComponent::new();
1188 let endpoint = component
1189 .create_endpoint("mock:body-text-pass", &NoOpComponentContext)
1190 .unwrap();
1191 let inner = component.get_endpoint("body-text-pass").unwrap();
1192 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1193 producer
1194 .call(Exchange::new(Message::new("hello")))
1195 .await
1196 .unwrap();
1197 inner
1198 .await_exchanges(1, std::time::Duration::from_millis(500))
1199 .await;
1200 inner.exchange(0).assert_body_text("hello");
1201 }
1202
1203 #[tokio::test(flavor = "multi_thread")]
1204 #[should_panic(expected = "expected body text")]
1205 async fn assert_body_text_fail() {
1206 let ctx = test_producer_ctx();
1207 let component = MockComponent::new();
1208 let endpoint = component
1209 .create_endpoint("mock:body-text-fail", &NoOpComponentContext)
1210 .unwrap();
1211 let inner = component.get_endpoint("body-text-fail").unwrap();
1212 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1213 producer
1214 .call(Exchange::new(Message::new("hello")))
1215 .await
1216 .unwrap();
1217 inner
1218 .await_exchanges(1, std::time::Duration::from_millis(500))
1219 .await;
1220 inner.exchange(0).assert_body_text("world");
1221 }
1222
1223 #[tokio::test(flavor = "multi_thread")]
1224 async fn assert_body_json_pass() {
1225 use camel_component_api::Body;
1226 let ctx = test_producer_ctx();
1227 let component = MockComponent::new();
1228 let endpoint = component
1229 .create_endpoint("mock:body-json-pass", &NoOpComponentContext)
1230 .unwrap();
1231 let inner = component.get_endpoint("body-json-pass").unwrap();
1232 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1233 let mut msg = Message::new("");
1234 msg.body = Body::Json(serde_json::json!({"key": "value"}));
1235 producer.call(Exchange::new(msg)).await.unwrap();
1236 inner
1237 .await_exchanges(1, std::time::Duration::from_millis(500))
1238 .await;
1239 inner
1240 .exchange(0)
1241 .assert_body_json(serde_json::json!({"key": "value"}));
1242 }
1243
1244 #[tokio::test(flavor = "multi_thread")]
1245 #[should_panic(expected = "expected body JSON")]
1246 async fn assert_body_json_fail() {
1247 use camel_component_api::Body;
1248 let ctx = test_producer_ctx();
1249 let component = MockComponent::new();
1250 let endpoint = component
1251 .create_endpoint("mock:body-json-fail", &NoOpComponentContext)
1252 .unwrap();
1253 let inner = component.get_endpoint("body-json-fail").unwrap();
1254 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1255 let mut msg = Message::new("");
1256 msg.body = Body::Json(serde_json::json!({"key": "value"}));
1257 producer.call(Exchange::new(msg)).await.unwrap();
1258 inner
1259 .await_exchanges(1, std::time::Duration::from_millis(500))
1260 .await;
1261 inner
1262 .exchange(0)
1263 .assert_body_json(serde_json::json!({"key": "other"}));
1264 }
1265
1266 #[tokio::test(flavor = "multi_thread")]
1267 async fn assert_body_bytes_pass() {
1268 use bytes::Bytes;
1269 use camel_component_api::Body;
1270 let ctx = test_producer_ctx();
1271 let component = MockComponent::new();
1272 let endpoint = component
1273 .create_endpoint("mock:body-bytes-pass", &NoOpComponentContext)
1274 .unwrap();
1275 let inner = component.get_endpoint("body-bytes-pass").unwrap();
1276 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1277 let mut msg = Message::new("");
1278 msg.body = Body::Bytes(Bytes::from_static(b"binary"));
1279 producer.call(Exchange::new(msg)).await.unwrap();
1280 inner
1281 .await_exchanges(1, std::time::Duration::from_millis(500))
1282 .await;
1283 inner.exchange(0).assert_body_bytes(b"binary");
1284 }
1285
1286 #[tokio::test(flavor = "multi_thread")]
1287 #[should_panic(expected = "expected body bytes")]
1288 async fn assert_body_bytes_fail() {
1289 use bytes::Bytes;
1290 use camel_component_api::Body;
1291 let ctx = test_producer_ctx();
1292 let component = MockComponent::new();
1293 let endpoint = component
1294 .create_endpoint("mock:body-bytes-fail", &NoOpComponentContext)
1295 .unwrap();
1296 let inner = component.get_endpoint("body-bytes-fail").unwrap();
1297 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1298 let mut msg = Message::new("");
1299 msg.body = Body::Bytes(Bytes::from_static(b"binary"));
1300 producer.call(Exchange::new(msg)).await.unwrap();
1301 inner
1302 .await_exchanges(1, std::time::Duration::from_millis(500))
1303 .await;
1304 inner.exchange(0).assert_body_bytes(b"different");
1305 }
1306
1307 #[tokio::test(flavor = "multi_thread")]
1308 async fn assert_header_pass() {
1309 let ctx = test_producer_ctx();
1310 let component = MockComponent::new();
1311 let endpoint = component
1312 .create_endpoint("mock:hdr-pass", &NoOpComponentContext)
1313 .unwrap();
1314 let inner = component.get_endpoint("hdr-pass").unwrap();
1315 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1316 let mut msg = Message::new("body");
1317 msg.headers
1318 .insert("x-key".to_string(), serde_json::json!("value"));
1319 producer.call(Exchange::new(msg)).await.unwrap();
1320 inner
1321 .await_exchanges(1, std::time::Duration::from_millis(500))
1322 .await;
1323 inner
1324 .exchange(0)
1325 .assert_header("x-key", serde_json::json!("value"));
1326 }
1327
1328 #[tokio::test(flavor = "multi_thread")]
1329 #[should_panic(expected = "expected header")]
1330 async fn assert_header_fail() {
1331 let ctx = test_producer_ctx();
1332 let component = MockComponent::new();
1333 let endpoint = component
1334 .create_endpoint("mock:hdr-fail", &NoOpComponentContext)
1335 .unwrap();
1336 let inner = component.get_endpoint("hdr-fail").unwrap();
1337 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1338 let mut msg = Message::new("body");
1339 msg.headers
1340 .insert("x-key".to_string(), serde_json::json!("value"));
1341 producer.call(Exchange::new(msg)).await.unwrap();
1342 inner
1343 .await_exchanges(1, std::time::Duration::from_millis(500))
1344 .await;
1345 inner
1346 .exchange(0)
1347 .assert_header("x-key", serde_json::json!("other"));
1348 }
1349
1350 #[tokio::test(flavor = "multi_thread")]
1351 async fn assert_header_exists_pass() {
1352 let ctx = test_producer_ctx();
1353 let component = MockComponent::new();
1354 let endpoint = component
1355 .create_endpoint("mock:hdr-exists-pass", &NoOpComponentContext)
1356 .unwrap();
1357 let inner = component.get_endpoint("hdr-exists-pass").unwrap();
1358 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1359 let mut msg = Message::new("body");
1360 msg.headers
1361 .insert("x-present".to_string(), serde_json::json!(42));
1362 producer.call(Exchange::new(msg)).await.unwrap();
1363 inner
1364 .await_exchanges(1, std::time::Duration::from_millis(500))
1365 .await;
1366 inner.exchange(0).assert_header_exists("x-present");
1367 }
1368
1369 #[tokio::test(flavor = "multi_thread")]
1370 #[should_panic(expected = "expected header")]
1371 async fn assert_header_exists_fail() {
1372 let ctx = test_producer_ctx();
1373 let component = MockComponent::new();
1374 let endpoint = component
1375 .create_endpoint("mock:hdr-exists-fail", &NoOpComponentContext)
1376 .unwrap();
1377 let inner = component.get_endpoint("hdr-exists-fail").unwrap();
1378 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1379 producer
1380 .call(Exchange::new(Message::new("body")))
1381 .await
1382 .unwrap();
1383 inner
1384 .await_exchanges(1, std::time::Duration::from_millis(500))
1385 .await;
1386 inner.exchange(0).assert_header_exists("x-missing");
1387 }
1388
1389 #[tokio::test(flavor = "multi_thread")]
1390 async fn assert_has_error_pass() {
1391 let ctx = test_producer_ctx();
1392 let component = MockComponent::new();
1393 let endpoint = component
1394 .create_endpoint("mock:err-pass", &NoOpComponentContext)
1395 .unwrap();
1396 let inner = component.get_endpoint("err-pass").unwrap();
1397 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1398 let mut ex = Exchange::new(Message::new("body"));
1399 ex.set_error(camel_component_api::CamelError::ProcessorError(
1400 "oops".to_string(),
1401 ));
1402 producer.call(ex).await.unwrap();
1403 inner
1404 .await_exchanges(1, std::time::Duration::from_millis(500))
1405 .await;
1406 inner.exchange(0).assert_has_error();
1407 }
1408
1409 #[tokio::test(flavor = "multi_thread")]
1410 #[should_panic(expected = "expected exchange to have an error")]
1411 async fn assert_has_error_fail() {
1412 let ctx = test_producer_ctx();
1413 let component = MockComponent::new();
1414 let endpoint = component
1415 .create_endpoint("mock:has-err-fail", &NoOpComponentContext)
1416 .unwrap();
1417 let inner = component.get_endpoint("has-err-fail").unwrap();
1418 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1419 producer
1420 .call(Exchange::new(Message::new("body")))
1421 .await
1422 .unwrap();
1423 inner
1424 .await_exchanges(1, std::time::Duration::from_millis(500))
1425 .await;
1426 inner.exchange(0).assert_has_error();
1427 }
1428
1429 #[tokio::test(flavor = "multi_thread")]
1430 async fn assert_no_error_pass() {
1431 let ctx = test_producer_ctx();
1432 let component = MockComponent::new();
1433 let endpoint = component
1434 .create_endpoint("mock:no-err-pass", &NoOpComponentContext)
1435 .unwrap();
1436 let inner = component.get_endpoint("no-err-pass").unwrap();
1437 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1438 producer
1439 .call(Exchange::new(Message::new("body")))
1440 .await
1441 .unwrap();
1442 inner
1443 .await_exchanges(1, std::time::Duration::from_millis(500))
1444 .await;
1445 inner.exchange(0).assert_no_error();
1446 }
1447
1448 #[tokio::test]
1453 async fn test_mock_reset_clears_exchanges() {
1454 let component = MockComponent::new();
1455 let endpoint = component
1456 .create_endpoint("mock:reset-test", &NoOpComponentContext)
1457 .unwrap();
1458 let inner = component.get_endpoint("reset-test").unwrap();
1459
1460 let ctx = test_producer_ctx();
1461 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1462 producer
1463 .call(Exchange::new(Message::new("a")))
1464 .await
1465 .unwrap();
1466 producer
1467 .call(Exchange::new(Message::new("b")))
1468 .await
1469 .unwrap();
1470
1471 assert_eq!(inner.received_count().await, 2);
1472 inner.reset().await;
1473 assert_eq!(inner.received_count().await, 0);
1474 }
1475
1476 #[tokio::test]
1477 async fn test_mock_bounded_retention_drops_oldest() {
1478 let config = MockConfig {
1479 max_retained: 3,
1480 ..Default::default()
1481 };
1482 let component = MockComponent::with_config(config);
1483 let endpoint = component
1484 .create_endpoint("mock:bounded", &NoOpComponentContext)
1485 .unwrap();
1486 let inner = component.get_endpoint("bounded").unwrap();
1487
1488 let ctx = test_producer_ctx();
1489 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1490
1491 for i in 0..5 {
1493 producer
1494 .call(Exchange::new(Message::new(format!("msg-{i}"))))
1495 .await
1496 .unwrap();
1497 }
1498
1499 assert_eq!(inner.received_count().await, 3);
1500 let received = inner.get_received_exchanges().await;
1501 assert_eq!(received[0].input.body.as_text(), Some("msg-2"));
1503 assert_eq!(received[1].input.body.as_text(), Some("msg-3"));
1504 assert_eq!(received[2].input.body.as_text(), Some("msg-4"));
1505 }
1506
1507 #[tokio::test]
1508 async fn test_mock_reset_then_record_again() {
1509 let component = MockComponent::new();
1510 let endpoint = component
1511 .create_endpoint("mock:reset-reuse", &NoOpComponentContext)
1512 .unwrap();
1513 let inner = component.get_endpoint("reset-reuse").unwrap();
1514
1515 let ctx = test_producer_ctx();
1516 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1517 producer
1518 .call(Exchange::new(Message::new("before-reset")))
1519 .await
1520 .unwrap();
1521 inner.reset().await;
1522
1523 producer
1524 .call(Exchange::new(Message::new("after-reset")))
1525 .await
1526 .unwrap();
1527
1528 let received = inner.get_received_exchanges().await;
1529 assert_eq!(received.len(), 1);
1530 assert_eq!(received[0].input.body.as_text(), Some("after-reset"));
1531 }
1532
1533 #[tokio::test(flavor = "multi_thread")]
1534 #[should_panic(expected = "expected exchange to have no error")]
1535 async fn assert_no_error_fail() {
1536 let ctx = test_producer_ctx();
1537 let component = MockComponent::new();
1538 let endpoint = component
1539 .create_endpoint("mock:no-err-fail", &NoOpComponentContext)
1540 .unwrap();
1541 let inner = component.get_endpoint("no-err-fail").unwrap();
1542 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1543 let mut ex = Exchange::new(Message::new("body"));
1544 ex.set_error(camel_component_api::CamelError::ProcessorError(
1545 "oops".to_string(),
1546 ));
1547 producer.call(ex).await.unwrap();
1548 inner
1549 .await_exchanges(1, std::time::Duration::from_millis(500))
1550 .await;
1551 inner.exchange(0).assert_no_error();
1552 }
1553
1554 #[tokio::test]
1559 async fn test_copy_on_exchange_stores_cloned_body() {
1560 let config = MockConfig {
1561 copy_on_exchange: true,
1562 ..Default::default()
1563 };
1564 let component = MockComponent::with_config(config);
1565 let endpoint = component
1566 .create_endpoint("mock:copy", &NoOpComponentContext)
1567 .unwrap();
1568 let inner = component.get_endpoint("copy").unwrap();
1569
1570 let ctx = test_producer_ctx();
1571 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1572
1573 let mut msg = Message::new("original");
1574 msg.headers.insert("x-test".into(), serde_json::json!(1));
1575 let ex = Exchange::new(msg);
1576 producer.call(ex).await.unwrap();
1577
1578 let received = inner.get_received_exchanges().await;
1579 assert_eq!(received[0].input.body.as_text(), Some("original"));
1580 }
1581
1582 #[tokio::test]
1583 async fn test_copy_on_exchange_false_shares_storage() {
1584 let config = MockConfig {
1585 copy_on_exchange: false,
1586 ..Default::default()
1587 };
1588 let component = MockComponent::with_config(config);
1589 let endpoint = component
1590 .create_endpoint("mock:no-copy", &NoOpComponentContext)
1591 .unwrap();
1592 let inner = component.get_endpoint("no-copy").unwrap();
1593
1594 let ctx = test_producer_ctx();
1595 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1596
1597 producer
1598 .call(Exchange::new(Message::new("direct")))
1599 .await
1600 .unwrap();
1601
1602 let received = inner.get_received_exchanges().await;
1603 assert_eq!(received[0].input.body.as_text(), Some("direct"));
1604 }
1605
1606 #[tokio::test]
1611 async fn test_assert_satisfied_bodies_in_order() {
1612 let component = MockComponent::new();
1613 let endpoint = component
1614 .create_endpoint("mock:sat-bodies", &NoOpComponentContext)
1615 .unwrap();
1616 let inner = component.get_endpoint("sat-bodies").unwrap();
1617
1618 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1619 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1620
1621 let ctx = test_producer_ctx();
1622 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1623 producer
1624 .call(Exchange::new(Message::new("alpha")))
1625 .await
1626 .unwrap();
1627 producer
1628 .call(Exchange::new(Message::new("beta")))
1629 .await
1630 .unwrap();
1631
1632 inner.assert_satisfied().await;
1633 }
1634
1635 #[tokio::test]
1636 #[should_panic(expected = "body[0] expected")]
1637 async fn test_assert_satisfied_bodies_wrong_order_fails() {
1638 let component = MockComponent::new();
1639 let endpoint = component
1640 .create_endpoint("mock:sat-bodies-fail", &NoOpComponentContext)
1641 .unwrap();
1642 let inner = component.get_endpoint("sat-bodies-fail").unwrap();
1643
1644 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1645 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1646
1647 let ctx = test_producer_ctx();
1648 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1649 producer
1650 .call(Exchange::new(Message::new("beta")))
1651 .await
1652 .unwrap();
1653 producer
1654 .call(Exchange::new(Message::new("alpha")))
1655 .await
1656 .unwrap();
1657
1658 inner.assert_satisfied().await;
1659 }
1660
1661 #[tokio::test]
1662 async fn test_assert_satisfied_headers() {
1663 let component = MockComponent::new();
1664 let endpoint = component
1665 .create_endpoint("mock:sat-hdr", &NoOpComponentContext)
1666 .unwrap();
1667 let inner = component.get_endpoint("sat-hdr").unwrap();
1668
1669 inner.expect_header("status", serde_json::json!("ok"));
1670
1671 let ctx = test_producer_ctx();
1672 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1673 let mut msg = Message::new("body");
1674 msg.headers.insert("status".into(), serde_json::json!("ok"));
1675 producer.call(Exchange::new(msg)).await.unwrap();
1676
1677 inner.assert_satisfied().await;
1678 }
1679
1680 #[tokio::test]
1681 #[should_panic(expected = "expected header 'missing' =")]
1682 async fn test_assert_satisfied_headers_missing() {
1683 let component = MockComponent::new();
1684 let endpoint = component
1685 .create_endpoint("mock:sat-hdr-missing", &NoOpComponentContext)
1686 .unwrap();
1687 let inner = component.get_endpoint("sat-hdr-missing").unwrap();
1688
1689 inner.expect_header("missing", serde_json::json!("value"));
1690
1691 let ctx = test_producer_ctx();
1692 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1693 producer
1694 .call(Exchange::new(Message::new("body")))
1695 .await
1696 .unwrap();
1697
1698 inner.assert_satisfied().await;
1699 }
1700
1701 #[tokio::test]
1706 async fn test_fail_fast_rejects_after_first_call() {
1707 let config = MockConfig {
1708 fail_fast: true,
1709 ..Default::default()
1710 };
1711 let component = MockComponent::with_config(config);
1712 let endpoint = component
1713 .create_endpoint("mock:ff", &NoOpComponentContext)
1714 .unwrap();
1715
1716 let ctx = test_producer_ctx();
1717 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1718
1719 producer
1721 .call(Exchange::new(Message::new("ok")))
1722 .await
1723 .unwrap();
1724 }
1725
1726 #[tokio::test]
1727 async fn test_fail_fast_no_error_when_all_good() {
1728 let config = MockConfig {
1729 fail_fast: true,
1730 ..Default::default()
1731 };
1732 let component = MockComponent::with_config(config);
1733 let endpoint = component
1734 .create_endpoint("mock:ff-good", &NoOpComponentContext)
1735 .unwrap();
1736 let inner = component.get_endpoint("ff-good").unwrap();
1737
1738 let ctx = test_producer_ctx();
1739 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1740
1741 producer
1742 .call(Exchange::new(Message::new("a")))
1743 .await
1744 .unwrap();
1745 producer
1746 .call(Exchange::new(Message::new("b")))
1747 .await
1748 .unwrap();
1749
1750 assert!(inner.fail_fast_error().is_none());
1751 inner.assert_exchange_count(2).await;
1752 }
1753
1754 #[tokio::test]
1759 async fn test_await_exchanges_with_timeout_uses_config_period() {
1760 let config = MockConfig {
1761 assert_period_ms: 100,
1762 ..Default::default()
1763 };
1764 let component = MockComponent::with_config(config);
1765 let endpoint = component
1766 .create_endpoint("mock:ap", &NoOpComponentContext)
1767 .unwrap();
1768 let inner = component.get_endpoint("ap").unwrap();
1769
1770 let ctx = test_producer_ctx();
1771 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1772 producer
1773 .call(Exchange::new(Message::new("x")))
1774 .await
1775 .unwrap();
1776
1777 inner
1778 .await_exchanges_with_timeout(1, std::time::Duration::from_millis(1))
1779 .await;
1780 }
1781
1782 #[tokio::test]
1783 async fn test_await_exchanges_with_timeout_uses_fallback_when_zero() {
1784 let config = MockConfig {
1785 assert_period_ms: 0,
1786 ..Default::default()
1787 };
1788 let component = MockComponent::with_config(config);
1789 let endpoint = component
1790 .create_endpoint("mock:ap-fb", &NoOpComponentContext)
1791 .unwrap();
1792 let inner = component.get_endpoint("ap-fb").unwrap();
1793
1794 let ctx = test_producer_ctx();
1795 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1796 producer
1797 .call(Exchange::new(Message::new("y")))
1798 .await
1799 .unwrap();
1800
1801 inner
1802 .await_exchanges_with_timeout(1, std::time::Duration::from_millis(200))
1803 .await;
1804 }
1805
1806 #[tokio::test]
1811 async fn test_expect_header_regex_match() {
1812 let component = MockComponent::new();
1813 let endpoint = component
1814 .create_endpoint("mock:re-hdr", &NoOpComponentContext)
1815 .unwrap();
1816 let inner = component.get_endpoint("re-hdr").unwrap();
1817
1818 inner.expect_header_regex("x-trace-id", r"^[a-f0-9]{8}$");
1819
1820 let ctx = test_producer_ctx();
1821 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1822 let mut msg = Message::new("body");
1823 msg.headers
1824 .insert("x-trace-id".into(), serde_json::json!("deadbeef"));
1825 producer.call(Exchange::new(msg)).await.unwrap();
1826
1827 inner.assert_satisfied().await;
1828 }
1829
1830 #[tokio::test]
1831 #[should_panic(expected = "no received exchange has header")]
1832 async fn test_expect_header_regex_no_match() {
1833 let component = MockComponent::new();
1834 let endpoint = component
1835 .create_endpoint("mock:re-hdr-fail", &NoOpComponentContext)
1836 .unwrap();
1837 let inner = component.get_endpoint("re-hdr-fail").unwrap();
1838
1839 inner.expect_header_regex("x-trace-id", r"^\d+$");
1840
1841 let ctx = test_producer_ctx();
1842 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1843 let mut msg = Message::new("body");
1844 msg.headers
1845 .insert("x-trace-id".into(), serde_json::json!("abc"));
1846 producer.call(Exchange::new(msg)).await.unwrap();
1847
1848 inner.assert_satisfied().await;
1849 }
1850
1851 #[tokio::test]
1856 async fn test_any_order_bodies_match() {
1857 let config = MockConfig {
1858 any_order: true,
1859 ..Default::default()
1860 };
1861 let component = MockComponent::with_config(config);
1862 let endpoint = component
1863 .create_endpoint("mock:anyorder", &NoOpComponentContext)
1864 .unwrap();
1865 let inner = component.get_endpoint("anyorder").unwrap();
1866
1867 inner.expect_body(camel_component_api::Body::Text("beta".into()));
1868 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
1869
1870 let ctx = test_producer_ctx();
1871 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1872 producer
1873 .call(Exchange::new(Message::new("alpha")))
1874 .await
1875 .unwrap();
1876 producer
1877 .call(Exchange::new(Message::new("beta")))
1878 .await
1879 .unwrap();
1880
1881 inner.assert_satisfied().await;
1882 }
1883
1884 #[tokio::test]
1885 #[should_panic(expected = "not found in received exchanges (anyOrder mode)")]
1886 async fn test_any_order_bodies_missing() {
1887 let config = MockConfig {
1888 any_order: true,
1889 ..Default::default()
1890 };
1891 let component = MockComponent::with_config(config);
1892 let endpoint = component
1893 .create_endpoint("mock:anyorder-fail", &NoOpComponentContext)
1894 .unwrap();
1895 let inner = component.get_endpoint("anyorder-fail").unwrap();
1896
1897 inner.expect_body(camel_component_api::Body::Text("gamma".into()));
1898 inner.expect_body(camel_component_api::Body::Text("alpha".into()));
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("alpha")))
1904 .await
1905 .unwrap();
1906 producer
1907 .call(Exchange::new(Message::new("beta")))
1908 .await
1909 .unwrap();
1910
1911 inner.assert_satisfied().await;
1912 }
1913
1914 #[tokio::test]
1919 async fn test_tracing_logs_exchange_received() {
1920 let ctx = test_producer_ctx();
1922 let component = MockComponent::new();
1923 let endpoint = component
1924 .create_endpoint("mock:trace", &NoOpComponentContext)
1925 .unwrap();
1926 let mut producer = endpoint.create_producer(rt(), &ctx).unwrap();
1927 producer
1928 .call(Exchange::new(Message::new("traced")))
1929 .await
1930 .unwrap();
1931
1932 let inner = component.get_endpoint("trace").unwrap();
1933 inner.assert_exchange_count(1).await;
1934 }
1935
1936 #[test]
1941 fn test_mock_config_new() {
1942 let cfg = MockConfig::new(42);
1943 assert_eq!(cfg.max_retained, 42);
1944 assert!(!cfg.copy_on_exchange);
1945 assert!(!cfg.fail_fast);
1946 assert!(!cfg.any_order);
1947 }
1948}