This is an automated email from the ASF dual-hosted git repository.
orpiske pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1cc74b3e4d38 (test): add quality test coverage for camel-rest component
1cc74b3e4d38 is described below
commit 1cc74b3e4d38be052ea5c296b555e3f0d8e440af
Author: Otavio Rodolfo Piske <[email protected]>
AuthorDate: Fri Jan 30 09:02:47 2026 +0000
(test): add quality test coverage for camel-rest component
Add unit tests for camel-rest component focusing on meaningful behavior
tests:
- RestComponent URI parsing and endpoint creation
- RestEndpoint behavior and error handling
- RestProducer query parameter resolution and exchange preparation
- RestApiEndpoint creation and factory interactions
- DefaultRestRegistry service management
- RestProducerBindingProcessor and callback behavior
Tests cover:
- Error conditions and exception handling
- URI template placeholder resolution
- Query parameter substitution with optional placeholders
- Host scheme handling and normalization
- Component property propagation
- Consumer/producer creation with factories
---
components/camel-rest/pom.xml | 28 ++
.../component/rest/DefaultRestRegistryTest.java | 198 ++++++++++
.../camel/component/rest/RestApiEndpointTest.java | 99 +++++
.../camel/component/rest/RestComponentTest.java | 200 ++++++++++
.../rest/RestEndpointProducerConsumerTest.java | 394 ++++++++++++++++++++
.../camel/component/rest/RestEndpointTest.java | 135 +++++++
.../component/rest/RestProducerAdvancedTest.java | 334 +++++++++++++++++
.../rest/RestProducerBindingCallbackTest.java | 402 +++++++++++++++++++++
.../rest/RestProducerBindingProcessorTest.java | 311 ++++++++++++++++
.../camel/component/rest/RestProducerTest.java | 354 ++++++++++++++++++
.../component/rest/RestRegistryStatefulTest.java | 148 ++++++++
11 files changed, 2603 insertions(+)
diff --git a/components/camel-rest/pom.xml b/components/camel-rest/pom.xml
index e78a91271f69..06c0ec97fffd 100644
--- a/components/camel-rest/pom.xml
+++ b/components/camel-rest/pom.xml
@@ -39,5 +39,33 @@
<artifactId>camel-support</artifactId>
</dependency>
+ <!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core-languages</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>${mockito-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
</project>
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
new file mode 100644
index 000000000000..136aab89d2f4
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultRestRegistryTest {
+
+ private DefaultRestRegistry registry;
+ private CamelContext camelContext;
+
+ @Mock
+ private Consumer consumer1;
+
+ @Mock
+ private Consumer consumer2;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ registry = new DefaultRestRegistry();
+ registry.setCamelContext(camelContext);
+ registry.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ registry.stop();
+ camelContext.stop();
+ }
+
+ @Test
+ void testAddRestService() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ "application/json", "application/json", "User", "User",
+ "route1", "Get all users");
+
+ assertThat(registry.size()).isEqualTo(1);
+ }
+
+ @Test
+ void testAddMultipleRestServices() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ "application/json", "application/json", null, null,
+ "route1", "Get all users");
+
+ registry.addRestService(consumer2, false,
"http://localhost:8080/api/orders",
+ "http://localhost:8080", "/api", "/orders", "POST",
+ "application/json", "application/json", "Order", "Order",
+ "route2", "Create order");
+
+ assertThat(registry.size()).isEqualTo(2);
+ }
+
+ @Test
+ void testAddMultipleServicesToSameConsumer() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users/{id}",
+ "http://localhost:8080", "/api", "/users/{id}", "GET",
+ null, null, null, null, "route2", "Get user by id");
+
+ assertThat(registry.size()).isEqualTo(2);
+ }
+
+ @Test
+ void testRemoveRestService() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ assertThat(registry.size()).isEqualTo(1);
+
+ registry.removeRestService(consumer1);
+
+ assertThat(registry.size()).isEqualTo(0);
+ }
+
+ @Test
+ void testRemoveNonExistentService() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ registry.removeRestService(consumer2);
+
+ assertThat(registry.size()).isEqualTo(1);
+ }
+
+ @Test
+ void testListAllRestServices() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ "application/json", "application/json", "User", "UserResponse",
+ "route1", "Get all users");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+
+ assertThat(services).hasSize(1);
+
+ RestRegistry.RestService service = services.get(0);
+
assertThat(service.getUrl()).isEqualTo("http://localhost:8080/api/users");
+ assertThat(service.getBaseUrl()).isEqualTo("http://localhost:8080");
+ assertThat(service.getBasePath()).isEqualTo("/api");
+ assertThat(service.getUriTemplate()).isEqualTo("/users");
+ assertThat(service.getMethod()).isEqualTo("GET");
+ assertThat(service.getConsumes()).isEqualTo("application/json");
+ assertThat(service.getProduces()).isEqualTo("application/json");
+ assertThat(service.getInType()).isEqualTo("User");
+ assertThat(service.getOutType()).isEqualTo("UserResponse");
+ assertThat(service.getDescription()).isEqualTo("Get all users");
+ assertThat(service.getConsumer()).isSameAs(consumer1);
+ assertThat(service.isContractFirst()).isFalse();
+ }
+
+ @Test
+ void testContractFirstService() {
+ registry.addRestService(consumer1, true,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ assertThat(services.get(0).isContractFirst()).isTrue();
+ }
+
+ @Test
+ void testSizeWithEmptyRegistry() {
+ assertThat(registry.size()).isEqualTo(0);
+ }
+
+ @Test
+ void testListAllRestServicesEmpty() {
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ assertThat(services).isEmpty();
+ }
+
+ @Test
+ void testServiceState() {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ // Non-stateful consumer returns Stopped state
+ assertThat(services.get(0).getState()).isEqualTo("Stopped");
+ }
+
+ @Test
+ void testApiDocAsJsonWithNoEndpoints() {
+ String apiDoc = registry.apiDocAsJson();
+ assertThat(apiDoc).isNull();
+ }
+
+ @Test
+ void testStopClearsRegistry() throws Exception {
+ registry.addRestService(consumer1, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ null, null, null, null, "route1", "Get users");
+
+ assertThat(registry.size()).isEqualTo(1);
+
+ registry.stop();
+
+ assertThat(registry.size()).isEqualTo(0);
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
new file mode 100644
index 000000000000..adf28eec9db2
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestApiEndpoint focusing on behavior and error handling.
+ */
+class RestApiEndpointTest {
+
+ private CamelContext camelContext;
+ private RestApiComponent component;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ component = new RestApiComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest-api", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testDefaultExchangePattern() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api-doc");
+
assertThat(endpoint.getExchangePattern()).isEqualTo(ExchangePattern.InOut);
+ }
+
+ @Test
+ void testIsNotRemote() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api-doc");
+ assertThat(endpoint.isRemote()).isFalse();
+ }
+
+ @Test
+ void testIsLenientProperties() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api-doc");
+ assertThat(endpoint.isLenientProperties()).isTrue();
+ }
+
+ @Test
+ void testCreateProducerWithoutFactory() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api-doc");
+
+ assertThatThrownBy(endpoint::createProducer)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Cannot find RestApiProcessorFactory");
+ }
+
+ @Test
+ void testCreateConsumerWithoutFactory() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api-doc");
+
+ assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+ }))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Cannot find RestApiConsumerFactory");
+ }
+
+ @Test
+ void testPathWithLeadingSlash() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:/api-doc");
+ assertThat(endpoint.getPath()).isEqualTo("/api-doc");
+ }
+
+ @Test
+ void testPathWithNestedPath() throws Exception {
+ RestApiEndpoint endpoint = (RestApiEndpoint)
camelContext.getEndpoint("rest-api:api/v2/doc");
+ assertThat(endpoint.getPath()).isEqualTo("api/v2/doc");
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java
new file mode 100644
index 000000000000..dbe30efb951b
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestComponent focusing on URI parsing and endpoint creation
behavior.
+ */
+class RestComponentTest {
+
+ private CamelContext camelContext;
+ private RestComponent component;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ component = new RestComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ // ==================== URI Parsing Tests ====================
+
+ @Test
+ void testCreateEndpointParsesMethodAndPath() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+ assertThat(endpoint).isInstanceOf(RestEndpoint.class);
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getMethod()).isEqualTo("get");
+ assertThat(restEndpoint.getPath()).isEqualTo("users");
+ assertThat(restEndpoint.getUriTemplate()).isNull();
+ }
+
+ @Test
+ void testCreateEndpointParsesMethodPathAndUriTemplate() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users:{id}?host=localhost:8080");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getMethod()).isEqualTo("get");
+ assertThat(restEndpoint.getPath()).isEqualTo("users");
+ assertThat(restEndpoint.getUriTemplate()).isEqualTo("{id}");
+ }
+
+ @Test
+ void testCreateEndpointSupportsAllHttpMethods() throws Exception {
+ String[] methods = { "get", "post", "put", "delete", "patch", "head",
"options" };
+
+ for (String method : methods) {
+ Endpoint endpoint = camelContext.getEndpoint("rest:" + method +
":test?host=localhost");
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getMethod()).isEqualTo(method);
+ }
+ }
+
+ @Test
+ void testCreateEndpointThrowsExceptionForInvalidSyntax() {
+ assertThatThrownBy(() -> camelContext.getEndpoint("rest:get"))
+ .hasMessageContaining("Invalid syntax");
+ }
+
+ @Test
+ void testCreateEndpointStripsTrailingSlashFromPath() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users/?host=localhost");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getPath()).isEqualTo("users");
+ }
+
+ // ==================== Host Handling Tests ====================
+
+ @Test
+ void testCreateEndpointAddsHttpPrefixWhenMissing() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getHost()).isEqualTo("http://localhost:8080");
+ }
+
+ @Test
+ void testCreateEndpointPreservesHttpScheme() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=http://localhost:8080");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getHost()).isEqualTo("http://localhost:8080");
+ }
+
+ @Test
+ void testCreateEndpointPreservesHttpsScheme() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=https://localhost:8443");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getHost()).isEqualTo("https://localhost:8443");
+ }
+
+ // ==================== Component Property Propagation Tests
====================
+
+ @Test
+ void testComponentConsumerNamePropagatestoEndpoint() throws Exception {
+ component.setConsumerComponentName("servlet");
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+
assertThat(restEndpoint.getConsumerComponentName()).isEqualTo("servlet");
+ }
+
+ @Test
+ void testComponentProducerNamePropagatestoEndpoint() throws Exception {
+ component.setProducerComponentName("undertow");
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+
assertThat(restEndpoint.getProducerComponentName()).isEqualTo("undertow");
+ }
+
+ @Test
+ void testComponentApiDocPropagatestoEndpoint() throws Exception {
+ component.setApiDoc("swagger.json");
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getApiDoc()).isEqualTo("swagger.json");
+ }
+
+ // ==================== Endpoint Options Tests ====================
+
+ @Test
+ void testCreateEndpointWithConsumesOption() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:post:users?host=localhost&consumes=application/json");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getConsumes()).isEqualTo("application/json");
+ }
+
+ @Test
+ void testCreateEndpointWithProducesOption() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost&produces=application/xml");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getProduces()).isEqualTo("application/xml");
+ }
+
+ @Test
+ void testCreateEndpointWithBindingModeOption() throws Exception {
+ Endpoint endpoint =
camelContext.getEndpoint("rest:get:users?host=localhost&bindingMode=json");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getBindingMode()).isNotNull();
+ }
+
+ @Test
+ void testCreateEndpointWithInTypeAndOutType() throws Exception {
+ Endpoint endpoint = camelContext.getEndpoint(
+
"rest:post:users?host=localhost&inType=com.example.User&outType=com.example.UserResponse");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getInType()).isEqualTo("com.example.User");
+
assertThat(restEndpoint.getOutType()).isEqualTo("com.example.UserResponse");
+ }
+
+ @Test
+ void testCreateEndpointWithComponentNames() throws Exception {
+ Endpoint endpoint = camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=jetty&producerComponentName=http");
+
+ RestEndpoint restEndpoint = (RestEndpoint) endpoint;
+ assertThat(restEndpoint.getConsumerComponentName()).isEqualTo("jetty");
+ assertThat(restEndpoint.getProducerComponentName()).isEqualTo("http");
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
new file mode 100644
index 000000000000..eeee8c86fa5c
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java
@@ -0,0 +1,394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestConsumerFactory;
+import org.apache.camel.spi.RestProducerFactory;
+import org.apache.camel.support.SimpleRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestEndpointProducerConsumerTest {
+
+ private CamelContext camelContext;
+ private SimpleRegistry registry;
+ private RestComponent component;
+
+ @Mock
+ private RestProducerFactory mockProducerFactory;
+
+ @Mock
+ private RestConsumerFactory mockConsumerFactory;
+
+ @Mock
+ private Producer mockProducer;
+
+ @Mock
+ private Consumer mockConsumer;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ registry = new SimpleRegistry();
+ camelContext = new DefaultCamelContext(registry);
+ component = new RestComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testCreateProducerWithRegisteredFactory() throws Exception {
+ registry.bind("myProducerFactory", mockProducerFactory);
+
+ when(mockProducerFactory.createProducer(
+ any(CamelContext.class), eq("http://localhost:8080"),
eq("get"),
+ eq("users"), any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockProducer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost:8080&producerComponentName=myProducerFactory");
+
+ Producer producer = endpoint.createProducer();
+
+ assertThat(producer).isNotNull();
+ assertThat(producer).isInstanceOf(RestProducer.class);
+ }
+
+ @Test
+ void testCreateProducerWithNonRestProducerFactoryComponent() throws
Exception {
+ registry.bind("myComponent", new Object());
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost:8080&producerComponentName=myComponent");
+
+ assertThatThrownBy(endpoint::createProducer)
+ .hasMessageContaining("RestProducerFactory");
+ }
+
+ @Test
+ void testCreateConsumerWithRegisteredFactory() throws Exception {
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost:8080&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithNonRestConsumerFactoryComponent() throws
Exception {
+ registry.bind("myComponent", new Object());
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost:8080&consumerComponentName=myComponent");
+
+ assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+ }))
+ .hasMessageContaining("RestConsumerFactory");
+ }
+
+ @Test
+ void testProducerWithConsumerFallback() throws Exception {
+ // Register a factory that implements both producer and consumer
+ RestProducerFactory dualFactory = mock(RestProducerFactory.class);
+ registry.bind("dualFactory", dualFactory);
+
+ when(dualFactory.createProducer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockProducer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost:8080&consumerComponentName=dualFactory");
+
+ // With no explicit producer component, it should fall back to
consumer component
+ Producer producer = endpoint.createProducer();
+
+ assertThat(producer).isNotNull();
+ }
+
+ @Test
+ void testCreateProducerWithEmptyHost() throws Exception {
+ RestEndpoint endpoint = new RestEndpoint("rest:get:users", component);
+ endpoint.setMethod("get");
+ endpoint.setPath("users");
+ endpoint.setHost("");
+ endpoint.setParameters(new HashMap<>());
+
+ assertThatThrownBy(endpoint::createProducer)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Hostname must be configured");
+ }
+
+ @Test
+ void testCreateConsumerWithRestConfiguration() throws Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+ restConfig.setHost("localhost");
+ restConfig.setPort(9090);
+ restConfig.setScheme("https");
+ restConfig.setContextPath("/api");
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithHostNameResolver() throws Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.localIp);
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithAllLocalIpResolver() throws Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.allLocalIp);
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithLocalHostNameResolver() throws Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+
restConfig.setHostNameResolver(RestConfiguration.RestHostNameResolver.localHostName);
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithUriTemplateStartingWithSlash() throws Exception
{
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users:/{id}?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithPathNotStartingWithSlash() throws Exception {
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithContextPathStartingWithSlash() throws Exception
{
+ RestConfiguration restConfig = new RestConfiguration();
+ restConfig.setContextPath("/api/v1");
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithContextPathNotStartingWithSlash() throws
Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+ restConfig.setContextPath("api/v1");
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithNonDefaultPort() throws Exception {
+ RestConfiguration restConfig = new RestConfiguration();
+ restConfig.setPort(9090);
+ camelContext.setRestConfiguration(restConfig);
+
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ }
+
+ @Test
+ void testCreateConsumerWithUriTemplate() throws Exception {
+ registry.bind("myConsumerFactory", mockConsumerFactory);
+
+ when(mockConsumerFactory.createConsumer(
+ any(CamelContext.class), any(), any(),
+ any(), any(), any(), any(),
+ any(RestConfiguration.class), any(Map.class)))
+ .thenReturn(mockConsumer);
+
+ RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint(
+
"rest:get:users:{id}?host=localhost&consumerComponentName=myConsumerFactory");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertThat(consumer).isNotNull();
+ assertThat(endpoint.getUriTemplate()).isEqualTo("{id}");
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
new file mode 100644
index 000000000000..e98ce4f6de33
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for RestEndpoint focusing on behavior and error handling.
+ */
+class RestEndpointTest {
+
+ private CamelContext camelContext;
+ private RestComponent component;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ component = new RestComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testCreateProducerWithoutHostThrowsIllegalArgumentException() throws
Exception {
+ RestEndpoint endpoint = new RestEndpoint("rest:get:users", component);
+ endpoint.setMethod("get");
+ endpoint.setPath("users");
+ endpoint.setParameters(new HashMap<>());
+
+ assertThatThrownBy(endpoint::createProducer)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Hostname must be configured");
+ }
+
+ @Test
+ void testCreateConsumerWithoutFactoryThrowsIllegalStateException() throws
Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ assertThatThrownBy(() -> endpoint.createConsumer(exchange -> {
+ }))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Cannot find RestConsumerFactory");
+ }
+
+ @Test
+ void testConfigurePropertiesMergesParametersMap() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ Map<String, Object> options = new HashMap<>();
+ Map<String, Object> params = new HashMap<>();
+ params.put("customKey", "customValue");
+ options.put("parameters", params);
+
+ endpoint.configureProperties(options);
+
+ assertThat(endpoint.getParameters()).containsEntry("customKey",
"customValue");
+ }
+
+ @Test
+ void testBindingModeCanBeSetFromString() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost&bindingMode=auto");
+
+
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.auto);
+ }
+
+ @Test
+ void testBindingModeCanBeSetFromEnum() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ endpoint.setBindingMode(RestConfiguration.RestBindingMode.json);
+
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.json);
+
+ endpoint.setBindingMode("xml");
+
assertThat(endpoint.getBindingMode()).isEqualTo(RestConfiguration.RestBindingMode.xml);
+ }
+
+ @Test
+ void testHostWithoutSchemeGetsHttpPrefixAdded() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost:8080");
+
+ assertThat(endpoint.getHost()).isEqualTo("http://localhost:8080");
+ }
+
+ @Test
+ void testHostWithHttpSchemeIsPreserved() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=http://localhost:8080");
+
+ assertThat(endpoint.getHost()).isEqualTo("http://localhost:8080");
+ }
+
+ @Test
+ void testHostWithHttpsSchemeIsPreserved() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=https://localhost:8443");
+
+ assertThat(endpoint.getHost()).isEqualTo("https://localhost:8443");
+ }
+
+ @Test
+ void testIsLenientPropertiesReturnsTrue() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+
+ assertThat(endpoint.isLenientProperties()).isTrue();
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
new file mode 100644
index 000000000000..9680e1d5d00e
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.HashMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerAdvancedTest {
+
+ private CamelContext camelContext;
+ private RestComponent component;
+
+ @Mock
+ private Producer mockProducer;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ component = new RestComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testPrepareExchangeWithMultiplePathPlaceholders() throws Exception {
+ // The colon is used in the URI syntax as path:uriTemplate, so we test
with slashes
+ RestEndpoint endpoint
+ = (RestEndpoint)
camelContext.getEndpoint("rest:get:users/{userId}/orders/{orderId}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("userId", "123");
+ exchange.getMessage().setHeader("orderId", "456");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost/users/123/orders/456");
+ }
+
+ @Test
+ void testPrepareExchangeWithUnresolvedPlaceholder() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users:{userId}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ // Don't set the header, so placeholder won't be resolved
+ producer.prepareExchange(exchange);
+
+ // When placeholder is not resolved, REST_HTTP_URI should not be set
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isNull();
+ }
+
+ @Test
+ void testPrepareExchangeWithMalformedPlaceholder() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users:{userId?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("userId", "123");
+ producer.prepareExchange(exchange);
+
+ // Malformed placeholder with unclosed brace should not crash
+ assertThat(exchange.getException()).isNull();
+ }
+
+ @Test
+ void testPrepareExchangeWithPathOnly() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+ // No template resolution needed, so no REST_HTTP_URI
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isNull();
+ }
+
+ @Test
+ void testPrepareExchangeWithBasePathAndTemplate() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:api/v1:users/{id}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "999");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost/api/v1/users/999");
+ }
+
+ @Test
+ void testPrepareExchangeWithEmptyQueryParameters() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setQueryParameters("");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+ String query =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+ // Empty query parameters result in empty string, not null
+ assertThat(query).isEmpty();
+ }
+
+ @Test
+ void testPrepareExchangePreservesExistingHeaders() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:post:users?host=localhost");
+ endpoint.setProduces("application/json");
+ endpoint.setConsumes("application/xml");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("Custom-Header", "custom-value");
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader("Custom-Header")).isEqualTo("custom-value");
+
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isEqualTo("POST");
+ }
+
+ @Test
+ void testPrepareExchangeWithAllMethodTypes() throws Exception {
+ String[] methods = { "get", "post", "put", "delete", "patch", "head",
"options", "trace", "connect" };
+
+ for (String method : methods) {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:" + method + ":test?host=localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD))
+ .isEqualTo(method.toUpperCase());
+ }
+ }
+
+ @Test
+ void testPrepareExchangeWithNullMethod() throws Exception {
+ RestEndpoint endpoint = new RestEndpoint("rest:null:test", component);
+ endpoint.setPath("test");
+ endpoint.setMethod(null);
+ endpoint.setHost("http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+ // Method is null, so HTTP_METHOD should not be set
+
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isNull();
+ }
+
+ @Test
+ void testPrepareExchangeWithNullProducesAndConsumes() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setProduces(null);
+ endpoint.setConsumes(null);
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+ // No content-type or accept headers should be set
+
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isNull();
+
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isNull();
+ }
+
+ @Test
+ void testProcessWithExceptionDuringPrepare() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ // Set query parameters that will cause an exception during parsing
+ endpoint.setQueryParameters("invalid=%%");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ boolean[] callbackDone = { false };
+
+ boolean result = producer.process(exchange, doneSync ->
callbackDone[0] = true);
+
+ assertThat(result).isTrue();
+ assertThat(callbackDone[0]).isTrue();
+ assertThat(exchange.getException()).isNotNull();
+ }
+
+ @Test
+ void testRestProducerLifecycle() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ config.setBindingMode(RestConfiguration.RestBindingMode.off);
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ // Test lifecycle
+ producer.doInit();
+ producer.doStart();
+
+ assertThat(producer.getEndpoint()).isSameAs(endpoint);
+
+ producer.doStop();
+ }
+
+ @Test
+ void testCreateQueryParametersWithTrailingAmpersand() throws Exception {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("a", "1");
+ // b is optional and not set
+
+ String result = RestProducer.createQueryParameters("a={a}&b={b?}",
exchange);
+
+ assertThat(result).isEqualTo("a=1");
+ assertThat(result).doesNotEndWith("&");
+ }
+
+ @Test
+ void testCreateQueryParametersWithAllOptionalMissing() throws Exception {
+ Exchange exchange = new DefaultExchange(camelContext);
+ // Don't set any headers
+
+ String result =
RestProducer.createQueryParameters("a={a?}&b={b?}&c={c?}", exchange);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testCreateQueryParametersWithMixedValues() throws Exception {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("a", "value1");
+ exchange.setVariable("c", "value3");
+ // b is optional and not set
+
+ String result =
RestProducer.createQueryParameters("a={a}&b={b?}&c={c}", exchange);
+
+ assertThat(result).contains("a=value1");
+ assertThat(result).contains("c=value3");
+ assertThat(result).doesNotContain("b=");
+ }
+
+ @Test
+ void testCreateQueryParametersWithSpecialCharacters() throws Exception {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("name", "John Doe");
+ exchange.getMessage().setHeader("email", "[email protected]");
+
+ String result =
RestProducer.createQueryParameters("name={name}&email={email}", exchange);
+
+ assertThat(result).contains("name=John+Doe");
+ assertThat(result).contains("email=john%40example.com");
+ }
+
+ @Test
+ void testPrepareExchangeWithLeadingSlashInUriTemplate() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:api:/{id}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "123");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost/api/123");
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
new file mode 100644
index 000000000000..2347e02ece73
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerBindingCallbackTest {
+
+ private CamelContext camelContext;
+
+ @Mock
+ private AsyncProcessor mockProcessor;
+
+ @Mock
+ private DataFormat jsonDataFormat;
+
+ @Mock
+ private DataFormat xmlDataFormat;
+
+ @Mock
+ private DataFormat outJsonDataFormat;
+
+ @Mock
+ private DataFormat outXmlDataFormat;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testCallbackWithJsonResponse() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithXmlResponse() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("<result>ok</result>");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/xml");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "xml", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackSkipsBindingOnErrorCode() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("Error");
+ exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE,
500);
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ // Body should not have been transformed due to error code skip
+
assertThat(exchange.getMessage().getBody(String.class)).isEqualTo("Error");
+ }
+
+ @Test
+ void testCallbackWithEmptyResponseBody() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody(null);
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithExceptionInExchange() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.setException(new RuntimeException("Test error"));
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ assertThat(exchange.getException()).isNotNull();
+ }
+
+ @Test
+ void testCallbackWithBindingModeOff() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "off", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithBindingModeAuto() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "auto", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithNoContentType() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ // No content type set
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ // Content-Type should be set by the callback
+
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("application/json");
+ }
+
+ @Test
+ void testCallbackWithXmlContentTypeWhenJsonMode() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("<result>ok</result>");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/xml");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithJsonXmlBindingMode() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json_xml", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWith3xxResponseCode() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("Redirect");
+ exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE,
302);
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithNoUnmarshallers() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("{\"result\": \"ok\"}");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, null, null,
+ null, null, "json", true, "com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ void testCallbackWithSkipBindingOnErrorCodeDisabled() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ doAnswer(invocation -> {
+ Exchange exchange = invocation.getArgument(0);
+ AsyncCallback callback = invocation.getArgument(1);
+ exchange.getMessage().setBody("Error");
+ exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE,
400);
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+ callback.done(true);
+ return true;
+ }).when(mockProcessor).process(any(Exchange.class),
any(AsyncCallback.class));
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", false,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ processor.process(exchange, doneSync -> callbackCalled.set(true));
+
+ assertThat(callbackCalled.get()).isTrue();
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
new file mode 100644
index 000000000000..dc9f7a3e9659
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestProducerBindingProcessorTest {
+
+ private CamelContext camelContext;
+
+ @Mock
+ private AsyncProcessor mockProcessor;
+
+ @Mock
+ private DataFormat jsonDataFormat;
+
+ @Mock
+ private DataFormat xmlDataFormat;
+
+ @Mock
+ private DataFormat outJsonDataFormat;
+
+ @Mock
+ private DataFormat outXmlDataFormat;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ @Test
+ void testProcessWithEmptyBody() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithStringBody() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("{\"name\": \"test\"}");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithByteArrayBody() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("{\"name\":
\"test\"}".getBytes(StandardCharsets.UTF_8));
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithInputStreamBody() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(new ByteArrayInputStream("{\"name\":
\"test\"}".getBytes(StandardCharsets.UTF_8)));
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithBindingModeOff() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, null, null, null, null, "off",
true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(new Object());
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithBindingModeAuto() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, null, null, null, null, "auto",
true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(new Object());
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithJsonContentType() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "auto", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithXmlContentType() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "auto", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/xml");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithOutType() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody(null);
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithSkipBindingOnErrorCodeEnabled() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", true,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 500);
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithSkipBindingOnErrorCodeDisabled() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, xmlDataFormat,
+ outJsonDataFormat, outXmlDataFormat, "json", false,
"com.example.Response");
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.HTTP_RESPONSE_CODE, 500);
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithNoDataFormats() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, null, null, null, null, "json",
true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("plain text");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithJsonBindingModeNoXml() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, jsonDataFormat, null,
+ outJsonDataFormat, null, "json", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/json");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ void testProcessWithXmlBindingModeNoJson() {
+ when(mockProcessor.process(any(Exchange.class),
any(AsyncCallback.class))).thenReturn(true);
+
+ RestProducerBindingProcessor processor = new
RestProducerBindingProcessor(
+ mockProcessor, camelContext, null, xmlDataFormat,
+ null, outXmlDataFormat, "xml", true, null);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setBody("test");
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"application/xml");
+
+ boolean result = processor.process(exchange, doneSync -> {
+ });
+
+ assertThat(result).isTrue();
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
new file mode 100644
index 000000000000..46f404028766
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.net.URISyntaxException;
+import java.util.HashMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Producer;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for RestProducer focusing on query parameter resolution and exchange
preparation.
+ */
+@ExtendWith(MockitoExtension.class)
+class RestProducerTest {
+
+ private CamelContext camelContext;
+ private RestComponent component;
+
+ @Mock
+ private Producer mockProducer;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ component = new RestComponent();
+ component.setCamelContext(camelContext);
+ camelContext.addComponent("rest", component);
+ camelContext.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ camelContext.stop();
+ }
+
+ // ==================== Query Parameter Resolution Tests
====================
+
+ @Test
+ void testCreateQueryParametersReturnsUnmodifiedQueryWhenNoPlaceholders()
throws URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ String result = RestProducer.createQueryParameters("page=1&size=10",
exchange);
+
+ assertThat(result).isEqualTo("page=1&size=10");
+ }
+
+ @Test
+ void testCreateQueryParametersResolvesPlaceholderFromHeader() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("userId", "123");
+
+ String result = RestProducer.createQueryParameters("id={userId}",
exchange);
+
+ assertThat(result).isEqualTo("id=123");
+ }
+
+ @Test
+ void testCreateQueryParametersResolvesMultiplePlaceholders() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("page", "2");
+ exchange.getMessage().setHeader("size", "50");
+
+ String result =
RestProducer.createQueryParameters("page={page}&size={size}", exchange);
+
+ assertThat(result).isEqualTo("page=2&size=50");
+ }
+
+ @Test
+ void testCreateQueryParametersRemovesOptionalPlaceholderWhenNotSet()
throws URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ String result = RestProducer.createQueryParameters("filter={filter?}",
exchange);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testCreateQueryParametersResolvesOptionalPlaceholderWhenSet() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("filter", "active");
+
+ String result = RestProducer.createQueryParameters("filter={filter?}",
exchange);
+
+ assertThat(result).isEqualTo("filter=active");
+ }
+
+ @Test
+ void testCreateQueryParametersHandlesMixedOptionalAndRequired() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("page", "1");
+ // filter is optional and not set
+
+ String result =
RestProducer.createQueryParameters("page={page}&filter={filter?}", exchange);
+
+ assertThat(result).isEqualTo("page=1");
+ }
+
+ @Test
+ void testCreateQueryParametersFallsBackToVariable() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setVariable("orderId", "456");
+
+ String result = RestProducer.createQueryParameters("order={orderId}",
exchange);
+
+ assertThat(result).isEqualTo("order=456");
+ }
+
+ @Test
+ void testCreateQueryParametersReturnsNullForNullInput() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ String result = RestProducer.createQueryParameters(null, exchange);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ void testCreateQueryParametersHandlesUrlEncodedPlaceholder() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("name", "John Doe");
+
+ String result = RestProducer.createQueryParameters("name=%7Bname%7D",
exchange);
+
+ assertThat(result).isEqualTo("name=John+Doe");
+ }
+
+ @Test
+ void testCreateQueryParametersKeepsUnresolvedRequiredPlaceholder() throws
URISyntaxException {
+ Exchange exchange = new DefaultExchange(camelContext);
+
+ String result = RestProducer.createQueryParameters("id={userId}",
exchange);
+
+ assertThat(result).isEqualTo("id=%7BuserId%7D");
+ }
+
+ // ==================== PrepareExchange Tests ====================
+
+ @Test
+ void testPrepareExchangeSetsHttpMethodHeader() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:post:users?host=localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.HTTP_METHOD)).isEqualTo("POST");
+ }
+
+ @Test
+ void testPrepareExchangeSetsContentTypeFromProduces() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:post:users?host=localhost");
+ endpoint.setProduces("application/json");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("application/json");
+ }
+
+ @Test
+ void testPrepareExchangeSetsAcceptFromConsumes() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setConsumes("application/xml");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isEqualTo("application/xml");
+ }
+
+ @Test
+ void testPrepareExchangeDoesNotOverrideExistingContentType() throws
Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:post:users?host=localhost");
+ endpoint.setProduces("application/json");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader(RestConstants.CONTENT_TYPE,
"text/plain");
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.CONTENT_TYPE)).isEqualTo("text/plain");
+ }
+
+ @Test
+ void testPrepareExchangeDoesNotOverrideExistingAccept() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setConsumes("application/xml");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader(RestConstants.ACCEPT, "text/html");
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(RestConstants.ACCEPT)).isEqualTo("text/html");
+ }
+
+ @Test
+ void testPrepareExchangeResolvesUriTemplateFromHeader() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost:8080");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "123");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost:8080/users/123");
+ }
+
+ @Test
+ void testPrepareExchangeResolvesUriTemplateFromVariable() throws Exception
{
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:orders:{orderId}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.setVariable("orderId", "789");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost/orders/789");
+ }
+
+ @Test
+ void testPrepareExchangeSetsQueryParametersHeader() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setQueryParameters("page=1&size=10");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ producer.prepareExchange(exchange);
+
+ String query =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+ assertThat(query).isEqualTo("page=1&size=10");
+ }
+
+ @Test
+ void testPrepareExchangeResolvesDynamicQueryParameters() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users?host=localhost");
+ endpoint.setQueryParameters("userId={id}");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "456");
+ producer.prepareExchange(exchange);
+
+ String query =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_QUERY, String.class);
+ assertThat(query).isEqualTo("userId=456");
+ }
+
+ @Test
+ void testPrepareExchangeRemovesHttpPathWhenUriIsSet() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "123");
+ exchange.getMessage().setHeader(Exchange.HTTP_PATH, "/old/path");
+ producer.prepareExchange(exchange);
+
+
assertThat(exchange.getMessage().getHeader(Exchange.HTTP_PATH)).isNull();
+ }
+
+ @Test
+ void testPrepareExchangeCombinesPathAndUriTemplate() throws Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:api:users/{id}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "999");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isEqualTo("http://localhost/api/users/999");
+ }
+
+ @Test
+ void
testPrepareExchangeDoesNotResolveTemplateWhenPrepareUriTemplateIsFalse() throws
Exception {
+ RestEndpoint endpoint = (RestEndpoint)
camelContext.getEndpoint("rest:get:users:{id}?host=http://localhost");
+ endpoint.setParameters(new HashMap<>());
+
+ RestConfiguration config = new RestConfiguration();
+ RestProducer producer = new RestProducer(endpoint, mockProducer,
config);
+ producer.setPrepareUriTemplate(false);
+
+ Exchange exchange = new DefaultExchange(camelContext);
+ exchange.getMessage().setHeader("id", "123");
+ producer.prepareExchange(exchange);
+
+ String uri =
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
+ assertThat(uri).isNull();
+ }
+}
diff --git
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
new file mode 100644
index 000000000000..4a7628ba52fb
--- /dev/null
+++
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.rest;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Processor;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.support.DefaultConsumer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RestRegistryStatefulTest {
+
+ private DefaultRestRegistry registry;
+ private CamelContext camelContext;
+
+ @Mock
+ private Endpoint mockEndpoint;
+
+ @Mock
+ private Processor mockProcessor;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ camelContext = new DefaultCamelContext();
+ camelContext.start();
+
+ // Configure mock endpoint to return the CamelContext
+ when(mockEndpoint.getCamelContext()).thenReturn(camelContext);
+
+ registry = new DefaultRestRegistry();
+ registry.setCamelContext(camelContext);
+ registry.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ registry.stop();
+ camelContext.stop();
+ }
+
+ @Test
+ void testServiceStateWithStatefulConsumer() throws Exception {
+ // Create a real DefaultConsumer which is a StatefulService
+ TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+
+ registry.addRestService(consumer, false,
"http://localhost:8080/api/users",
+ "http://localhost:8080", "/api", "/users", "GET",
+ "application/json", "application/json", null, null,
+ "route1", "Get users");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ assertThat(services).hasSize(1);
+
+ // Before starting, status should be Stopped
+ assertThat(services.get(0).getState()).isEqualTo("Stopped");
+
+ // Start the consumer
+ consumer.start();
+
+ // After starting, status should be Started
+ assertThat(services.get(0).getState()).isEqualTo("Started");
+
+ // Stop the consumer
+ consumer.stop();
+
+ // After stopping, status should be Stopped
+ assertThat(services.get(0).getState()).isEqualTo("Stopped");
+ }
+
+ @Test
+ void testServiceStateWithStartedStatefulConsumer() throws Exception {
+ TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+ consumer.start();
+
+ registry.addRestService(consumer, false,
"http://localhost:8080/api/orders",
+ "http://localhost:8080", "/api", "/orders", "POST",
+ null, null, null, null, "route2", "Create order");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ assertThat(services.get(0).getState()).isEqualTo("Started");
+
+ consumer.stop();
+ }
+
+ @Test
+ void testServiceStateWithSuspendedConsumer() throws Exception {
+ TestConsumer consumer = new TestConsumer(mockEndpoint, mockProcessor);
+ consumer.start();
+ consumer.suspend();
+
+ registry.addRestService(consumer, false,
"http://localhost:8080/api/items",
+ "http://localhost:8080", "/api", "/items", "DELETE",
+ null, null, null, null, "route3", "Delete item");
+
+ List<RestRegistry.RestService> services =
registry.listAllRestServices();
+ assertThat(services.get(0).getState()).isEqualTo("Suspended");
+
+ consumer.resume();
+ consumer.stop();
+ }
+
+ /**
+ * Test consumer that extends DefaultConsumer for testing stateful service
behavior
+ */
+ private static class TestConsumer extends DefaultConsumer {
+
+ public TestConsumer(Endpoint endpoint, Processor processor) {
+ super(endpoint, processor);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ // No-op for test
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ // No-op for test
+ }
+ }
+}