use std::sync::Arc;
use std::sync::Mutex;
use camel_api::{Exchange, Message, RouteController};
use camel_component_direct::DirectComponent;
use camel_component_mock::MockComponent;
use camel_component_seda::SedaComponent;
use camel_core::intercept::{InterceptAction, InterceptRule, InterceptRules};
use camel_core::route::BuilderStep;
use camel_core::route_controller::DefaultRouteController;
use camel_core::{CamelContext, Registry, RouteDefinition};
use tower::ServiceExt;
use crate::common::{
TEST_TIMEOUT, boot_context_with_intercept, raw_seda_producer, send_awaiting_consumers,
send_to_direct, send_to_direct_result, test_rt,
};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exact_uri_match_with_first_match_wins() {
let rules = InterceptRules::new(vec![
InterceptRule {
uri: "seda:out".into(),
action: InterceptAction::SkipTo {
uri: "mock:first".into(),
},
},
InterceptRule {
uri: "seda:out".into(),
action: InterceptAction::SkipTo {
uri: "mock:second".into(),
},
},
])
.expect("valid mock targets");
let (mut ctx, mock) = boot_context_with_intercept(Some(rules)).await;
ctx.add_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("seda:out".into())])
.with_route_id("first-match-wins"),
)
.await
.expect("route must register");
ctx.start().await.expect("context start failed");
send_to_direct(&ctx, "direct:in", Exchange::new(Message::new("hello"))).await;
let first = mock
.get_endpoint("first")
.expect("mock endpoint 'first' must exist");
first.await_exchanges(1, TEST_TIMEOUT).await;
first.assert_exchange_count(1).await;
assert!(
mock.get_endpoint("second").is_none(),
"the second rule must never be consulted: mock:second endpoint must not exist"
);
ctx.stop().await.expect("context stop failed");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn empty_rule_set_leaves_the_send_untouched() {
let (mut ctx_empty, mock_empty) =
boot_context_with_intercept(Some(InterceptRules::default())).await;
let (mut ctx_plain, mock_plain) = boot_context_with_intercept(None).await;
for (ctx, mock) in [(&mut ctx_empty, &mock_empty), (&mut ctx_plain, &mock_plain)] {
ctx.add_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("seda:out".into())])
.with_route_id("send-route"),
)
.await
.expect("send route must register");
ctx.add_route_definition(
RouteDefinition::new("seda:out", vec![BuilderStep::To("mock:arrival".into())])
.with_route_id("consumer-route"),
)
.await
.expect("consumer route must register");
ctx.start().await.expect("context start failed");
send_awaiting_consumers("direct call", || {
send_to_direct_result(ctx, "direct:in", Exchange::new(Message::new("sentinel")))
})
.await;
let arrival = mock
.get_endpoint("arrival")
.expect("mock endpoint 'arrival' must exist");
arrival.await_exchanges(1, TEST_TIMEOUT).await;
arrival.assert_exchange_count(1).await;
let received = arrival.get_received_exchanges().await;
assert_eq!(
received[0].input.body.as_text(),
Some("sentinel"),
"consumer must record the sentinel body unchanged"
);
ctx.stop().await.expect("context stop failed");
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn skipped_uri_with_unregistered_real_component() {
let rules = InterceptRules::new(vec![InterceptRule {
uri: "kafka:orders".into(),
action: InterceptAction::SkipTo {
uri: "mock:orders".into(),
},
}])
.expect("valid mock targets");
let (mut ctx, mock) = boot_context_with_intercept(Some(rules)).await;
ctx.add_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("kafka:orders".into())])
.with_route_id("unregistered-source"),
)
.await
.expect("route must register despite the unregistered kafka component");
ctx.start().await.expect("context start failed");
send_to_direct(&ctx, "direct:in", Exchange::new(Message::new("order"))).await;
let orders = mock
.get_endpoint("orders")
.expect("mock endpoint 'orders' must exist");
orders.await_exchanges(1, TEST_TIMEOUT).await;
orders.assert_exchange_count(1).await;
ctx.stop().await.expect("context stop failed");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn skip_target_resolution_failure_is_a_compile_error() {
let rules = InterceptRules::new(vec![InterceptRule {
uri: "kafka:x".into(),
action: InterceptAction::SkipTo {
uri: "mock:x".into(),
},
}])
.expect("valid mock targets");
let mut ctx = CamelContext::builder()
.build()
.await
.expect("build context");
ctx.register_component(DirectComponent::new());
ctx.set_intercept_rules(rules)
.await
.expect("rules must install before first use");
let err = ctx
.add_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("kafka:x".into())])
.with_route_id("unresolvable-target"),
)
.await
.expect_err("route must fail to compile: mock:x cannot resolve");
let msg = format!("{err}");
assert!(
msg.contains("mock:x"),
"error must name the intercept target, got: {msg}"
);
assert!(
msg.contains("intercept target:"),
"error must carry the intercept target enrichment, got: {msg}"
);
ctx.stop().await.expect("context stop failed");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn skip_replaces_the_enqueue() {
let rules = InterceptRules::new(vec![InterceptRule {
uri: "seda:q".into(),
action: InterceptAction::SkipTo {
uri: "mock:q".into(),
},
}])
.expect("valid mock targets");
let (mut ctx, mock) = boot_context_with_intercept(Some(rules)).await;
ctx.add_route_definition(
RouteDefinition::new("seda:q", vec![BuilderStep::To("mock:sink".into())])
.with_route_id("sink-route"),
)
.await
.expect("consumer route must register");
ctx.add_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("seda:q".into())])
.with_route_id("send-route"),
)
.await
.expect("send route must register");
ctx.start().await.expect("context start failed");
send_to_direct(&ctx, "direct:in", Exchange::new(Message::new("sentinel"))).await;
let q = mock
.get_endpoint("q")
.expect("mock endpoint 'q' must exist");
q.await_exchanges(1, TEST_TIMEOUT).await;
q.assert_exchange_count(1).await;
let producer = raw_seda_producer(&ctx, "seda:q");
send_awaiting_consumers("seda enqueue", || {
producer
.clone()
.oneshot(Exchange::new(Message::new("BARRIER")))
})
.await;
let sink = mock
.get_endpoint("sink")
.expect("mock endpoint 'sink' must exist");
sink.await_exchanges(1, TEST_TIMEOUT).await;
sink.assert_exchange_count(1).await;
let received = sink.get_received_exchanges().await;
assert_eq!(
received[0].input.body.as_text(),
Some("BARRIER"),
"sink must record the BARRIER, not the intercepted sentinel"
);
ctx.stop().await.expect("context stop failed");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn recompiled_pipelines_keep_the_same_rules() {
let rules = InterceptRules::new(vec![InterceptRule {
uri: "seda:out".into(),
action: InterceptAction::SkipTo {
uri: "mock:q".into(),
},
}])
.expect("valid mock targets");
let mock = MockComponent::new();
let registry = Arc::new(Mutex::new(Registry::new()));
{
let mut guard = registry.lock().expect("registry lock");
guard.register(Arc::new(mock.clone()));
guard.register(Arc::new(DirectComponent::new()));
guard.register(Arc::new(SedaComponent::new()));
}
let mut controller = DefaultRouteController::new(
Arc::clone(®istry),
Arc::new(camel_api::NoopPlatformService::default()),
)
.with_intercept_rules(rules);
let def = RouteDefinition::new("direct:in", vec![BuilderStep::To("seda:out".into())])
.with_route_id("recompile-route");
controller
.add_route(def)
.await
.expect("route must register");
controller
.start_route("recompile-route")
.await
.expect("route must start");
controller.activate_cohort(); controller.activate_cohort();
let recompiled = controller
.compile_route_definition(
RouteDefinition::new("direct:in", vec![BuilderStep::To("seda:out".into())])
.with_route_id("recompile-route"),
)
.expect("recompile must succeed");
controller
.swap_pipeline("recompile-route", recompiled)
.expect("swap must succeed");
let direct = registry
.lock()
.expect("registry lock")
.get("direct")
.expect("direct component registered");
let endpoint = direct
.create_endpoint("direct:in", &camel_component_api::NoOpComponentContext)
.expect("failed to create direct endpoint");
let producer = endpoint
.create_producer(test_rt(), &camel_component_api::ProducerContext::new())
.expect("failed to create direct producer");
producer
.oneshot(Exchange::new(Message::new("after-recompile")))
.await
.expect("direct call should succeed");
let q = mock
.get_endpoint("q")
.expect("mock endpoint 'q' must exist");
q.await_exchanges(1, TEST_TIMEOUT).await;
q.assert_exchange_count(1).await;
controller
.stop_route("recompile-route")
.await
.expect("route stop failed");
}