From ccef33032ade6dd4ceed7a4ee8588d570acc49f1 Mon Sep 17 00:00:00 2001 From: Otavio Rodolfo Piske Date: Fri, 30 Jan 2026 09:02:47 +0000 Subject: [PATCH] (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 - RestApiComponent and 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 Removed low-quality tests that only verified getters/setters or constants. --- components/camel-rest/pom.xml | 23 + .../rest/DefaultRestRegistryTest.java | 198 +++++++++ .../component/rest/RestApiComponentTest.java | 100 +++++ .../rest/RestApiEndpointFactoryTest.java | 268 ++++++++++++ .../component/rest/RestApiEndpointTest.java | 99 +++++ .../component/rest/RestComponentTest.java | 200 +++++++++ .../component/rest/RestConfigurerTest.java | 193 +++++++++ .../RestEndpointProducerConsumerTest.java | 394 +++++++++++++++++ .../component/rest/RestEndpointTest.java | 135 ++++++ .../rest/RestProducerAdvancedTest.java | 334 +++++++++++++++ .../rest/RestProducerBindingCallbackTest.java | 402 ++++++++++++++++++ .../RestProducerBindingProcessorTest.java | 311 ++++++++++++++ .../component/rest/RestProducerTest.java | 354 +++++++++++++++ .../rest/RestRegistryStatefulTest.java | 148 +++++++ 14 files changed, 3159 insertions(+) create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiComponentTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointFactoryTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestComponentTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestConfigurerTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointProducerConsumerTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestEndpointTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingCallbackTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerBindingProcessorTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerTest.java create mode 100644 components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java diff --git a/components/camel-rest/pom.xml b/components/camel-rest/pom.xml index e78a91271f69e..f165758b49c7e 100644 --- a/components/camel-rest/pom.xml +++ b/components/camel-rest/pom.xml @@ -39,5 +39,28 @@ camel-support + + + org.apache.camel + camel-test-junit5 + test + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-junit-jupiter + ${mockito-version} + test + + + org.assertj + assertj-core + test + + 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 0000000000000..136aab89d2f42 --- /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 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 services = registry.listAllRestServices(); + assertThat(services.get(0).isContractFirst()).isTrue(); + } + + @Test + void testSizeWithEmptyRegistry() { + assertThat(registry.size()).isEqualTo(0); + } + + @Test + void testListAllRestServicesEmpty() { + List 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 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/RestApiComponentTest.java b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiComponentTest.java new file mode 100644 index 0000000000000..55cc958909fbf --- /dev/null +++ b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiComponentTest.java @@ -0,0 +1,100 @@ +/* + * 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; + +class RestApiComponentTest { + + 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 testCreateEndpoint() throws Exception { + Endpoint endpoint = camelContext.getEndpoint("rest-api:api-doc"); + + assertThat(endpoint).isNotNull(); + assertThat(endpoint).isInstanceOf(RestApiEndpoint.class); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + assertThat(restApiEndpoint.getPath()).isEqualTo("api-doc"); + } + + @Test + void testCreateEndpointWithPath() throws Exception { + Endpoint endpoint = camelContext.getEndpoint("rest-api:swagger/v2"); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + assertThat(restApiEndpoint.getPath()).isEqualTo("swagger/v2"); + } + + @Test + void testCreateEndpointWithConsumerComponentName() throws Exception { + component.setConsumerComponentName("undertow"); + Endpoint endpoint = camelContext.getEndpoint("rest-api:api-doc"); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + assertThat(restApiEndpoint.getConsumerComponentName()).isEqualTo("undertow"); + } + + @Test + void testCreateEndpointWithApiComponentName() throws Exception { + Endpoint endpoint = camelContext.getEndpoint("rest-api:api-doc?apiComponentName=swagger"); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + assertThat(restApiEndpoint.getApiComponentName()).isEqualTo("swagger"); + } + + @Test + void testCreateEndpointWithConsumerComponentNameParam() throws Exception { + Endpoint endpoint = camelContext.getEndpoint("rest-api:api-doc?consumerComponentName=netty-http"); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + assertThat(restApiEndpoint.getConsumerComponentName()).isEqualTo("netty-http"); + } + + @Test + void testComponentConsumerNameOverriddenByParam() throws Exception { + component.setConsumerComponentName("jetty"); + Endpoint endpoint = camelContext.getEndpoint("rest-api:api-doc?consumerComponentName=servlet"); + + RestApiEndpoint restApiEndpoint = (RestApiEndpoint) endpoint; + // Parameter should override component setting + assertThat(restApiEndpoint.getConsumerComponentName()).isEqualTo("servlet"); + } +} diff --git a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointFactoryTest.java b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointFactoryTest.java new file mode 100644 index 0000000000000..eaa1b8357042e --- /dev/null +++ b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestApiEndpointFactoryTest.java @@ -0,0 +1,268 @@ +/* + * 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.NoSuchBeanException; +import org.apache.camel.Processor; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.spi.RestApiConsumerFactory; +import org.apache.camel.spi.RestApiProcessorFactory; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.support.DefaultComponent; +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 RestApiEndpointFactoryTest { + + private CamelContext camelContext; + private RestApiComponent component; + private SimpleRegistry registry; + + @Mock + private Consumer mockConsumer; + + @Mock + private Processor mockProcessor; + + @BeforeEach + void setUp() throws Exception { + registry = new SimpleRegistry(); + camelContext = new DefaultCamelContext(registry); + component = new RestApiComponent(); + component.setCamelContext(camelContext); + camelContext.addComponent("rest-api", component); + camelContext.start(); + } + + @AfterEach + void tearDown() throws Exception { + camelContext.stop(); + } + + @Test + void testCreateConsumerWithExplicitConsumerComponentName() throws Exception { + // Register a mock RestApiConsumerFactory in registry + RestApiConsumerFactory mockFactory = mock(RestApiConsumerFactory.class); + when(mockFactory.createApiConsumer( + any(CamelContext.class), + any(Processor.class), + eq("/api-doc"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockConsumer); + + registry.bind("myApiFactory", mockFactory); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setConsumerComponentName("myApiFactory"); + endpoint.setParameters(new HashMap<>()); + + Consumer consumer = endpoint.createConsumer(exchange -> { + }); + assertThat(consumer).isNotNull(); + } + + @Test + void testCreateConsumerWithNonRestApiConsumerFactoryComponentThrowsException() throws Exception { + // Add a component that is not a RestApiConsumerFactory + TestNonRestApiComponent nonRestApiComponent = new TestNonRestApiComponent(); + nonRestApiComponent.setCamelContext(camelContext); + camelContext.addComponent("non-api-component", nonRestApiComponent); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setConsumerComponentName("non-api-component"); + endpoint.setParameters(new HashMap<>()); + + assertThatThrownBy(() -> endpoint.createConsumer(exchange -> { + })) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("is not a RestApiConsumerFactory"); + } + + @Test + void testCreateConsumerWithNonExistentComponentThrowsNoSuchBeanException() throws Exception { + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setConsumerComponentName("nonexistent"); + endpoint.setParameters(new HashMap<>()); + + assertThatThrownBy(() -> endpoint.createConsumer(exchange -> { + })) + .isInstanceOf(NoSuchBeanException.class); + } + + @Test + void testCreateConsumerWithRestApiConsumerFactoryComponent() throws Exception { + // Create a component that implements RestApiConsumerFactory + RestApiConsumerFactory mockFactory = mock(RestApiConsumerFactory.class); + when(mockFactory.createApiConsumer( + any(CamelContext.class), + any(Processor.class), + eq("/api-doc"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockConsumer); + + // Create a component wrapper + TestRestApiComponent testComponent = new TestRestApiComponent(mockFactory); + testComponent.setCamelContext(camelContext); + camelContext.addComponent("test-api-component", testComponent); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setConsumerComponentName("test-api-component"); + endpoint.setParameters(new HashMap<>()); + + Consumer consumer = endpoint.createConsumer(exchange -> { + }); + assertThat(consumer).isNotNull(); + } + + @Test + void testCreateConsumerWithFactoryInRegistry() throws Exception { + // Register a RestApiConsumerFactory in registry (not as a component) + RestApiConsumerFactory mockFactory = mock(RestApiConsumerFactory.class); + when(mockFactory.createApiConsumer( + any(CamelContext.class), + any(Processor.class), + eq("/api-doc"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockConsumer); + + registry.bind("apiFactory", mockFactory); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setParameters(new HashMap<>()); + + Consumer consumer = endpoint.createConsumer(exchange -> { + }); + assertThat(consumer).isNotNull(); + } + + @Test + void testCreateConsumerWithPathWithoutLeadingSlash() throws Exception { + RestApiConsumerFactory mockFactory = mock(RestApiConsumerFactory.class); + when(mockFactory.createApiConsumer( + any(CamelContext.class), + any(Processor.class), + eq("/openapi"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockConsumer); + + registry.bind("apiFactory", mockFactory); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:openapi"); + endpoint.setParameters(new HashMap<>()); + + Consumer consumer = endpoint.createConsumer(exchange -> { + }); + assertThat(consumer).isNotNull(); + } + + @Test + void testCreateProducerWithFactoryInRegistry() throws Exception { + RestApiProcessorFactory mockFactory = mock(RestApiProcessorFactory.class); + Processor mockApiProcessor = mock(Processor.class); + when(mockFactory.createApiProcessor( + any(CamelContext.class), + eq("/api-doc"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockApiProcessor); + + registry.bind("apiProcessorFactory", mockFactory); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:api-doc"); + endpoint.setParameters(new HashMap<>()); + + var producer = endpoint.createProducer(); + assertThat(producer).isNotNull(); + assertThat(producer).isInstanceOf(RestApiProducer.class); + } + + @Test + void testCreateProducerWithApiComponentNameFromConfig() throws Exception { + RestApiProcessorFactory mockFactory = mock(RestApiProcessorFactory.class); + Processor mockApiProcessor = mock(Processor.class); + when(mockFactory.createApiProcessor( + any(CamelContext.class), + eq("/swagger"), + any(RestConfiguration.class), + any(Map.class))).thenReturn(mockApiProcessor); + + registry.bind("apiProcessorFactory", mockFactory); + + RestConfiguration config = new RestConfiguration(); + config.setApiComponent("customApi"); + camelContext.setRestConfiguration(config); + + RestApiEndpoint endpoint = (RestApiEndpoint) camelContext.getEndpoint("rest-api:swagger"); + endpoint.setParameters(new HashMap<>()); + + var producer = endpoint.createProducer(); + assertThat(producer).isNotNull(); + } + + /** + * Test component that implements RestApiConsumerFactory + */ + private static class TestRestApiComponent extends DefaultComponent implements RestApiConsumerFactory { + + private final RestApiConsumerFactory delegate; + + TestRestApiComponent(RestApiConsumerFactory delegate) { + this.delegate = delegate; + } + + @Override + public Consumer createApiConsumer( + CamelContext camelContext, Processor processor, String contextPath, + RestConfiguration configuration, Map parameters) + throws Exception { + return delegate.createApiConsumer(camelContext, processor, contextPath, configuration, parameters); + } + + @Override + protected org.apache.camel.Endpoint createEndpoint(String uri, String remaining, Map parameters) { + return null; + } + } + + /** + * Test component that does NOT implement RestApiConsumerFactory + */ + private static class TestNonRestApiComponent extends DefaultComponent { + + @Override + protected org.apache.camel.Endpoint createEndpoint(String uri, String remaining, Map parameters) { + return null; + } + } +} 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 0000000000000..adf28eec9db2e --- /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 0000000000000..dbe30efb951b1 --- /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/RestConfigurerTest.java b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestConfigurerTest.java new file mode 100644 index 0000000000000..e7b09b90530ad --- /dev/null +++ b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestConfigurerTest.java @@ -0,0 +1,193 @@ +/* + * 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.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; + +class RestConfigurerTest { + + 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 testRestComponentConfigurerGetOptionType() { + RestComponentConfigurer configurer = new RestComponentConfigurer(); + + assertThat(configurer.getOptionType("consumerComponentName", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("producerComponentName", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("apiDoc", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("host", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("nonexistent", false)).isNull(); + } + + @Test + void testRestComponentConfigurerCaseInsensitive() { + RestComponentConfigurer configurer = new RestComponentConfigurer(); + + assertThat(configurer.getOptionType("CONSUMERCOMPONENTNAME", true)).isEqualTo(String.class); + assertThat(configurer.getOptionType("APIDOC", true)).isEqualTo(String.class); + } + + @Test + void testRestComponentConfigurerConfigure() { + RestComponentConfigurer configurer = new RestComponentConfigurer(); + + assertThat(configurer.configure(camelContext, component, "consumerComponentName", "jetty", false)).isTrue(); + assertThat(component.getConsumerComponentName()).isEqualTo("jetty"); + + assertThat(configurer.configure(camelContext, component, "producerComponentName", "http", false)).isTrue(); + assertThat(component.getProducerComponentName()).isEqualTo("http"); + + assertThat(configurer.configure(camelContext, component, "apiDoc", "swagger.json", false)).isTrue(); + assertThat(component.getApiDoc()).isEqualTo("swagger.json"); + + assertThat(configurer.configure(camelContext, component, "host", "localhost:8080", false)).isTrue(); + assertThat(component.getHost()).isEqualTo("localhost:8080"); + + assertThat(configurer.configure(camelContext, component, "nonexistent", "value", false)).isFalse(); + } + + @Test + void testRestComponentConfigurerGetOptionValue() { + RestComponentConfigurer configurer = new RestComponentConfigurer(); + + component.setConsumerComponentName("jetty"); + component.setProducerComponentName("http"); + component.setApiDoc("openapi.json"); + component.setHost("localhost:9090"); + + assertThat(configurer.getOptionValue(component, "consumerComponentName", false)).isEqualTo("jetty"); + assertThat(configurer.getOptionValue(component, "producerComponentName", false)).isEqualTo("http"); + assertThat(configurer.getOptionValue(component, "apiDoc", false)).isEqualTo("openapi.json"); + assertThat(configurer.getOptionValue(component, "host", false)).isEqualTo("localhost:9090"); + assertThat(configurer.getOptionValue(component, "nonexistent", false)).isNull(); + } + + @Test + void testRestEndpointConfigurerGetOptionType() throws Exception { + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.getOptionType("consumes", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("produces", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("host", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("routeId", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("description", false)).isEqualTo(String.class); + assertThat(configurer.getOptionType("nonexistent", false)).isNull(); + } + + @Test + void testRestEndpointConfigurerConfigure() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.configure(camelContext, endpoint, "consumes", "application/json", false)).isTrue(); + assertThat(endpoint.getConsumes()).isEqualTo("application/json"); + + assertThat(configurer.configure(camelContext, endpoint, "produces", "application/xml", false)).isTrue(); + assertThat(endpoint.getProduces()).isEqualTo("application/xml"); + + assertThat(configurer.configure(camelContext, endpoint, "routeId", "myRoute", false)).isTrue(); + assertThat(endpoint.getRouteId()).isEqualTo("myRoute"); + + assertThat(configurer.configure(camelContext, endpoint, "description", "Test description", false)).isTrue(); + assertThat(endpoint.getDescription()).isEqualTo("Test description"); + + assertThat(configurer.configure(camelContext, endpoint, "inType", "com.example.Input", false)).isTrue(); + assertThat(endpoint.getInType()).isEqualTo("com.example.Input"); + + assertThat(configurer.configure(camelContext, endpoint, "outType", "com.example.Output", false)).isTrue(); + assertThat(endpoint.getOutType()).isEqualTo("com.example.Output"); + + assertThat(configurer.configure(camelContext, endpoint, "nonexistent", "value", false)).isFalse(); + } + + @Test + void testRestEndpointConfigurerGetOptionValue() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + endpoint.setConsumes("application/json"); + endpoint.setProduces("application/xml"); + endpoint.setRouteId("testRoute"); + endpoint.setDescription("Test endpoint"); + + assertThat(configurer.getOptionValue(endpoint, "consumes", false)).isEqualTo("application/json"); + assertThat(configurer.getOptionValue(endpoint, "produces", false)).isEqualTo("application/xml"); + assertThat(configurer.getOptionValue(endpoint, "routeId", false)).isEqualTo("testRoute"); + assertThat(configurer.getOptionValue(endpoint, "description", false)).isEqualTo("Test endpoint"); + assertThat(configurer.getOptionValue(endpoint, "nonexistent", false)).isNull(); + } + + @Test + void testRestEndpointConfigurerConfigureBindingMode() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.configure(camelContext, endpoint, "bindingMode", "json", false)).isTrue(); + assertThat(endpoint.getBindingMode()).isNotNull(); + } + + @Test + void testRestEndpointConfigurerConfigureApiDoc() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.configure(camelContext, endpoint, "apiDoc", "openapi.yaml", false)).isTrue(); + assertThat(endpoint.getApiDoc()).isEqualTo("openapi.yaml"); + } + + @Test + void testRestEndpointConfigurerConfigureQueryParameters() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.configure(camelContext, endpoint, "queryParameters", "page=1&size=10", false)).isTrue(); + assertThat(endpoint.getQueryParameters()).isEqualTo("page=1&size=10"); + } + + @Test + void testRestEndpointConfigurerConfigureComponentNames() throws Exception { + RestEndpoint endpoint = (RestEndpoint) camelContext.getEndpoint("rest:get:users?host=localhost"); + RestEndpointConfigurer configurer = new RestEndpointConfigurer(); + + assertThat(configurer.configure(camelContext, endpoint, "consumerComponentName", "undertow", false)).isTrue(); + assertThat(endpoint.getConsumerComponentName()).isEqualTo("undertow"); + + assertThat(configurer.configure(camelContext, endpoint, "producerComponentName", "vertx-http", false)).isTrue(); + assertThat(endpoint.getProducerComponentName()).isEqualTo("vertx-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 0000000000000..eeee8c86fa5c7 --- /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 0000000000000..e98ce4f6de332 --- /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 options = new HashMap<>(); + Map 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 0000000000000..9680e1d5d00ed --- /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", "john@example.com"); + + 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 0000000000000..2347e02ece731 --- /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("ok"); + 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("ok"); + 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 0000000000000..dc9f7a3e96594 --- /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 0000000000000..46f4040287668 --- /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 0000000000000..4a7628ba52fbe --- /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 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 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 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 + } + } +}