diff --git a/components/camel-amqp/pom.xml b/components/camel-amqp/pom.xml index dbc80840c5b74..141cbd5d5c358 100644 --- a/components/camel-amqp/pom.xml +++ b/components/camel-amqp/pom.xml @@ -75,6 +75,14 @@ + + org.apache.camel + camel-test-infra-core + ${project.version} + test + test-jar + + org.apache.camel camel-test-infra-artemis diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTest.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTest.java index 9ccb3c7dbdf48..0e8281eabffb6 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTest.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTest.java @@ -26,12 +26,15 @@ import jakarta.jms.TextMessage; import org.apache.camel.CamelContext; -import org.apache.camel.EndpointInject; +import org.apache.camel.ProducerTemplate; import org.apache.camel.RuntimeCamelException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.qpid.jms.message.JmsMessage; import org.apache.qpid.jms.provider.amqp.message.AmqpJmsMessageFacade; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.apache.camel.component.amqp.AMQPComponent.amqpComponent; @@ -40,15 +43,22 @@ public class AMQPRouteTest extends AMQPTestSupport { - @EndpointInject("mock:result") - MockEndpoint resultEndpoint; + private ProducerTemplate template; - String expectedBody = "Hello there!"; + private MockEndpoint resultEndpoint; + private String expectedBody = "Hello there!"; + + @BeforeEach + void setupTemplate() { + template = contextExtension.getProducerTemplate(); + resultEndpoint = contextExtension.getMockEndpoint("mock:result"); + } @Test public void testJmsQueue() throws Exception { resultEndpoint.expectedMessageCount(1); resultEndpoint.message(0).header("cheese").isEqualTo(123); + template.sendBodyAndHeader("amqp-customized:queue:ping", expectedBody, "cheese", 123); resultEndpoint.assertIsSatisfied(); } @@ -88,7 +98,7 @@ public void testNoAmqpAnnotations() throws Exception { resultEndpoint.message(0).header("cheese").isEqualTo(123); // default doesn't map annotations to headers resultEndpoint.message(0).header("JMS_AMQP_MA_cheese").isNull(); - sendAmqpMessage(context.getComponent("amqp-customized", AMQPComponent.class), + sendAmqpMessage(contextExtension.getContext().getComponent("amqp-customized", AMQPComponent.class), "ping", expectedBody, facade -> { try { facade.setApplicationProperty("cheese", 123); @@ -105,7 +115,7 @@ public void testAmqpAnnotations() throws Exception { resultEndpoint.expectedMessageCount(1); resultEndpoint.message(0).header("cheese").isEqualTo(123); resultEndpoint.message(0).header("JMS_AMQP_MA_cheese").isEqualTo(456); - sendAmqpMessage(context.getComponent("amqp-customized2", AMQPComponent.class), + sendAmqpMessage(contextExtension.getContext().getComponent("amqp-customized2", AMQPComponent.class), "ping2", expectedBody, facade -> { try { facade.setApplicationProperty("cheese", 123); @@ -131,21 +141,22 @@ private void sendAmqpMessage( } } - // Routes fixtures + @ContextFixture + public void configureContext(CamelContext context) { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext camelContext = super.createCamelContext(); - camelContext.getRegistry().bind("amqpConnection", discoverAMQP(camelContext)); + context.getRegistry().bind("amqpConnection", discoverAMQP(context)); + context.addComponent("amqp-customized", amqpComponent(service.serviceAddress())); + context.addComponent("amqp-customized2", amqpComponent(service.serviceAddress())); + context.getComponent("amqp-customized2", AMQPComponent.class).setIncludeAmqpAnnotations(true); + } - camelContext.addComponent("amqp-customized", amqpComponent(service.serviceAddress())); - camelContext.addComponent("amqp-customized2", amqpComponent(service.serviceAddress())); - camelContext.getComponent("amqp-customized2", AMQPComponent.class).setIncludeAmqpAnnotations(true); - return camelContext; + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); } - @Override - protected RouteBuilder createRouteBuilder() { + private static RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { from("amqp-customized:queue:ping") diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTraceFrameTest.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTraceFrameTest.java index 601ca9a0680de..f4c63d6d01fad 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTraceFrameTest.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPRouteTraceFrameTest.java @@ -17,35 +17,55 @@ package org.apache.camel.component.amqp; import org.apache.camel.CamelContext; -import org.apache.camel.EndpointInject; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.artemis.services.ArtemisService; +import org.apache.camel.test.infra.artemis.services.ArtemisServiceFactory; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.qpid.jms.JmsConnectionFactory; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.apache.camel.component.amqp.AMQPComponent.amqpComponent; import static org.apache.camel.component.amqp.AMQPConnectionDetails.discoverAMQP; public class AMQPRouteTraceFrameTest extends AMQPTestSupport { - @EndpointInject("mock:result") - MockEndpoint resultEndpoint; + @RegisterExtension + protected static ArtemisService service = ArtemisServiceFactory.createSingletonAMQPService(); - String expectedBody = "Hello there!"; + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + + private MockEndpoint resultEndpoint; + + private String expectedBody = "Hello there!"; + private ProducerTemplate template; + + @BeforeEach + void setupTemplate() { + resultEndpoint = contextExtension.getMockEndpoint("mock:result"); + template = contextExtension.getProducerTemplate(); + } @Test public void testTraceFrame() throws Exception { resultEndpoint.expectedMessageCount(1); resultEndpoint.message(0).header("cheese").isEqualTo(123); - template.sendBodyAndHeader("amqp-customized:queue:ping", expectedBody, "cheese", 123); + + template.sendBodyAndHeader("amqp-with-trace:queue:ping", expectedBody, "cheese", 123); resultEndpoint.assertIsSatisfied(); } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext camelContext = super.createCamelContext(); - - camelContext.getRegistry().bind("amqpConnection", discoverAMQP(camelContext)); + @ContextFixture + public void configureContext(CamelContext context) { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); + context.getRegistry().bind("amqpConnection", discoverAMQP(context)); JmsConnectionFactory connectionFactory = new JmsConnectionFactory(service.serviceAddress() + "?amqp.traceFrames=true"); @@ -53,18 +73,17 @@ protected CamelContext createCamelContext() throws Exception { AMQPComponent amqp = amqpComponent(service.serviceAddress()); amqp.getConfiguration().setConnectionFactory(connectionFactory); - camelContext.addComponent("amqp-customized", amqp); - return camelContext; + context.addComponent("amqp-with-trace", amqp); } - @Override - protected RouteBuilder createRouteBuilder() { - return new RouteBuilder() { + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(new RouteBuilder() { public void configure() { - from("amqp-customized:queue:ping") + from("amqp-with-trace:queue:ping") .to("log:routing") .to("mock:result"); } - }; + }); } } diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPTestSupport.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPTestSupport.java index 266cecf99a02e..60790fcd0fb11 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPTestSupport.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPTestSupport.java @@ -14,21 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.camel.component.amqp; import org.apache.camel.test.infra.artemis.services.ArtemisService; import org.apache.camel.test.infra.artemis.services.ArtemisServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.junit.jupiter.api.BeforeAll; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.api.ConfigurableContext; +import org.apache.camel.test.infra.core.api.ConfigurableRoute; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; -public class AMQPTestSupport extends CamelTestSupport { - +public abstract class AMQPTestSupport implements ConfigurableContext, ConfigurableRoute { + @Order(1) @RegisterExtension protected static ArtemisService service = ArtemisServiceFactory.createSingletonAMQPService(); - @BeforeAll - public static void beforeAll() { - System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); - } + @Order(2) + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); } diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDSendDynamicTest.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDSendDynamicTest.java index 545da3dc36089..96a987deadd93 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDSendDynamicTest.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDSendDynamicTest.java @@ -17,13 +17,21 @@ package org.apache.camel.component.amqp; import org.apache.camel.CamelContext; +import org.apache.camel.ConsumerTemplate; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.RouteFixture; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.apache.camel.component.amqp.AMQPConnectionDetails.discoverAMQP; import static org.junit.jupiter.api.Assertions.assertEquals; public class AMQPToDSendDynamicTest extends AMQPTestSupport { + private ProducerTemplate template; + private ConsumerTemplate consumer; @Test public void testToD() { @@ -31,7 +39,8 @@ public void testToD() { template.sendBodyAndHeader("direct:start", "Hello beer", "where", "beer"); // there should only be one amqp endpoint - long count = context.getEndpoints().stream().filter(e -> e.getEndpointUri().startsWith("amqp:")).count(); + long count = contextExtension.getContext().getEndpoints().stream().filter(e -> e.getEndpointUri().startsWith("amqp:")) + .count(); assertEquals(1, count, "There should only be 1 amqp endpoint"); // and the messages should be in the queues @@ -41,15 +50,30 @@ public void testToD() { assertEquals("Hello beer", out); } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext camelContext = super.createCamelContext(); + @BeforeAll + static void startContext() throws Exception { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); + } + + @BeforeEach + void setupTemplate() { + template = contextExtension.getProducerTemplate(); + consumer = contextExtension.getConsumerTemplate(); + } + + @ContextFixture + public void configureContext(CamelContext camelContext) { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); + camelContext.getRegistry().bind("amqpConnection", discoverAMQP(camelContext)); - return camelContext; } - @Override - protected RouteBuilder createRouteBuilder() { + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + + private RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { // route message dynamic using toD diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDTest.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDTest.java index 8243a637e7557..294176345205b 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDTest.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/AMQPToDTest.java @@ -17,34 +17,54 @@ package org.apache.camel.component.amqp; import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.RouteFixture; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.apache.camel.component.amqp.AMQPConnectionDetails.discoverAMQP; public class AMQPToDTest extends AMQPTestSupport { + private ProducerTemplate template; @Test public void testToD() throws Exception { - getMockEndpoint("mock:bar").expectedBodiesReceived("Hello bar"); - getMockEndpoint("mock:beer").expectedBodiesReceived("Hello beer"); + contextExtension.getMockEndpoint("mock:bar").expectedBodiesReceived("Hello bar"); + contextExtension.getMockEndpoint("mock:beer").expectedBodiesReceived("Hello beer"); template.sendBodyAndHeader("direct:start", "Hello bar", "where", "bar"); template.sendBodyAndHeader("direct:start", "Hello beer", "where", "beer"); - MockEndpoint.assertIsSatisfied(context); + MockEndpoint.assertIsSatisfied(contextExtension.getContext()); } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext camelContext = super.createCamelContext(); + @BeforeAll + static void startContext() throws Exception { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); + } + + @BeforeEach + void setupTemplate() { + template = contextExtension.getProducerTemplate(); + } + + @ContextFixture + public void configureContext(CamelContext camelContext) { + System.setProperty(AMQPConnectionDetails.AMQP_PORT, service.brokerPort() + ""); + camelContext.getRegistry().bind("amqpConnection", discoverAMQP(camelContext)); - return camelContext; } - @Override - protected RouteBuilder createRouteBuilder() { + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + + private RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { // route message dynamic using toD diff --git a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/artemis/AMQPEmbeddedBrokerTest.java b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/artemis/AMQPEmbeddedBrokerTest.java index 5654f4b5c1b7c..8ae8d95622316 100644 --- a/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/artemis/AMQPEmbeddedBrokerTest.java +++ b/components/camel-amqp/src/test/java/org/apache/camel/component/amqp/artemis/AMQPEmbeddedBrokerTest.java @@ -19,15 +19,17 @@ import java.util.concurrent.TimeUnit; import org.apache.camel.CamelContext; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.amqp.AMQPComponent; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.test.infra.artemis.services.ArtemisService; import org.apache.camel.test.infra.artemis.services.ArtemisServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.junit.jupiter.api.BeforeAll; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -35,26 +37,26 @@ import static org.apache.camel.component.amqp.AMQPConnectionDetails.AMQP_SET_TOPIC_PREFIX; import static org.apache.camel.component.amqp.AMQPConnectionDetails.discoverAMQP; -public class AMQPEmbeddedBrokerTest extends CamelTestSupport { +public class AMQPEmbeddedBrokerTest { + @Order(1) @RegisterExtension - static ArtemisService service = ArtemisServiceFactory.createSingletonAMQPService(); + public static ArtemisService service = ArtemisServiceFactory.createSingletonAMQPService(); - @EndpointInject("mock:result") - MockEndpoint resultEndpoint; + @Order(2) + @RegisterExtension + public static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); - String expectedBody = "Hello there!"; + private MockEndpoint resultEndpoint; - @BeforeAll - public static void beforeClass() throws Exception { - System.setProperty(AMQP_PORT, service.brokerPort() + ""); - System.setProperty(AMQP_SET_TOPIC_PREFIX, "false"); - } + private String expectedBody = "Hello there!"; @BeforeEach void prepareTest() { + resultEndpoint = contextExtension.getMockEndpoint("mock:result"); + resultEndpoint.expectedMessageCount(1); - template.sendBody("direct:send-topic", expectedBody); + contextExtension.getProducerTemplate().sendBody("direct:send-topic", expectedBody); } @Test @@ -62,16 +64,21 @@ public void testTopicWithoutPrefix() throws Exception { resultEndpoint.assertIsSatisfied(10, TimeUnit.SECONDS); } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext camelContext = super.createCamelContext(); - camelContext.getRegistry().bind("amqpConnection", discoverAMQP(camelContext)); - camelContext.addComponent("amqp-customized", new AMQPComponent()); - return camelContext; + @ContextFixture + public static void setupRoutes(CamelContext context) throws Exception { + System.setProperty(AMQP_PORT, service.brokerPort() + ""); + System.setProperty(AMQP_SET_TOPIC_PREFIX, "false"); + + context.getRegistry().bind("amqpConnection", discoverAMQP(context)); + context.addComponent("amqp-customized", new AMQPComponent()); + } + + @RouteFixture + public static void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); } - @Override - protected RouteBuilder createRouteBuilder() { + private static RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { from("direct:send-topic") diff --git a/components/camel-kafka/pom.xml b/components/camel-kafka/pom.xml index 9404f7b857d73..a3df7100dc924 100644 --- a/components/camel-kafka/pom.xml +++ b/components/camel-kafka/pom.xml @@ -68,6 +68,14 @@ test + + org.apache.camel + camel-test-infra-core + ${project.version} + test + test-jar + + org.apache.camel camel-test-infra-kafka @@ -95,6 +103,10 @@ test + org.apache.camel camel-test-junit5 diff --git a/components/camel-kafka/src/main/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepository.java b/components/camel-kafka/src/main/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepository.java index 8402d955e2a75..20de5f0128e5c 100644 --- a/components/camel-kafka/src/main/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepository.java +++ b/components/camel-kafka/src/main/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepository.java @@ -359,6 +359,8 @@ public boolean add(String key) { private void broadcastAction(String key, CacheAction action) { try { log.debug("Broadcasting action:{} for key:{}", action, key); + assert producer != null; + producer.send(new ProducerRecord<>(topic, key, action.toString())).get(); // sync // send } catch (ExecutionException | InterruptedException e) { diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaAutowireTest.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaAutowireTest.java index 81bf1fbf5b2ba..c629f7aaeb5c9 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaAutowireTest.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaAutowireTest.java @@ -17,15 +17,23 @@ package org.apache.camel.component.kafka; import org.apache.camel.BindToRegistry; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.CamelContext; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.jupiter.api.Assertions.assertSame; -public class KafkaAutowireTest extends CamelTestSupport { +public class KafkaAutowireTest { + + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + + private CamelContext context = contextExtension.getContext(); @BindToRegistry - KafkaClientFactory clientFactory = new TestKafkaClientFactory(); + private KafkaClientFactory clientFactory = new TestKafkaClientFactory(); @Test public void testKafkaComponentAutowiring() { diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaComponentTest.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaComponentTest.java index f01da289f0c47..97147d48096f0 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaComponentTest.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/KafkaComponentTest.java @@ -20,20 +20,34 @@ import java.util.Map; import java.util.Properties; +import org.apache.camel.CamelContext; import org.apache.camel.support.jsse.KeyStoreParameters; import org.apache.camel.support.jsse.SSLContextParameters; import org.apache.camel.support.jsse.TrustManagersParameters; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.config.SaslConfigs; import org.apache.kafka.common.config.SslConfigs; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -public class KafkaComponentTest extends CamelTestSupport { +public class KafkaComponentTest { + + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + + private CamelContext context = contextExtension.getContext(); + + @AfterEach + void clear() { + context.removeComponent("kafka"); + } @Test public void testPropertiesSet() { diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaTestSupport.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaTestSupport.java index 02e27dbefb229..c6bc92a6cbaf0 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaTestSupport.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaTestSupport.java @@ -19,46 +19,67 @@ import java.util.Properties; import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.KafkaComponent; +import org.apache.camel.component.kafka.integration.common.KafkaAdminUtil; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; +import org.apache.camel.test.infra.core.api.ConfigurableRoute; import org.apache.camel.test.infra.kafka.services.KafkaService; import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; import org.apache.kafka.clients.admin.AdminClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class BaseEmbeddedKafkaTestSupport extends AbstractKafkaTestSupport { +public abstract class BaseEmbeddedKafkaTestSupport implements ConfigurableRoute { + @Order(1) @RegisterExtension - public static KafkaService service = KafkaServiceFactory.createSingletonService(); + protected static KafkaService service = KafkaServiceFactory.createSingletonService(); + + @Order(2) + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); protected static AdminClient kafkaAdminClient; - @BeforeAll - public static void beforeClass() { - AbstractKafkaTestSupport.setServiceProperties(service); + @BeforeEach + public void beforeClass() { + KafkaTestUtil.setServiceProperties(service); } @BeforeEach public void setKafkaAdminClient() { if (kafkaAdminClient == null) { - kafkaAdminClient = createAdminClient(); + kafkaAdminClient = KafkaAdminUtil.createAdminClient(service); } } - protected Properties getDefaultProperties() { - return getDefaultProperties(service); - } + @ContextFixture + public void configureKafka(CamelContext context) { + context.getPropertiesComponent().setLocation("ref:prop"); - @Override - protected CamelContext createCamelContext() throws Exception { - return createCamelContextFromService(service); + KafkaComponent kafka = new KafkaComponent(context); + kafka.init(); + kafka.getConfiguration().setBrokers(service.getBootstrapServers()); + context.addComponent("kafka", kafka); } - protected static String getBootstrapServers() { - return service.getBootstrapServers(); + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); } - private static AdminClient createAdminClient() { - return createAdminClient(service); + protected abstract RouteBuilder createRouteBuilder(); + + protected Properties getDefaultProperties() { + return KafkaTestUtil.getDefaultProperties(service); } + protected static String getBootstrapServers() { + return service.getBootstrapServers(); + } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAsyncManualCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAsyncManualCommitIT.java index 9f01dfd3540d3..06e6b5ac1e286 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAsyncManualCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAsyncManualCommitIT.java @@ -21,8 +21,7 @@ import java.util.concurrent.TimeUnit; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.builder.AggregationStrategies; import org.apache.camel.builder.RouteBuilder; @@ -30,6 +29,7 @@ import org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; import org.apache.camel.component.kafka.consumer.KafkaManualCommitFactory; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.awaitility.Awaitility; @@ -47,33 +47,23 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - public class KafkaConsumerAsyncManualCommitIT extends BaseEmbeddedKafkaTestSupport { public static final String TOPIC = "testManualCommitTest"; private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAsyncManualCommitIT.class); - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerAsyncManualCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" - + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#testFactory") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - - @EndpointInject("mock:resultBar") - private MockEndpoint toBar; - @BindToRegistry("testFactory") private KafkaManualCommitFactory manualCommitFactory = new DefaultKafkaManualAsyncCommitFactory(); + private CamelContext context = contextExtension.getContext(); + private org.apache.kafka.clients.producer.KafkaProducer producer; private volatile int failCount; @BeforeEach public void before() { - Properties props = getDefaultProperties(); + Properties props = KafkaTestUtil.getDefaultProperties(service); producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); } @@ -84,17 +74,20 @@ public void after() { } } - @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to("direct:aggregate"); + String uri = "kafka:" + TOPIC + "?brokers=" + service.getBootstrapServers() + + "&groupId=KafkaConsumerAsyncManualCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#testFactory"; + + from(uri).routeId("foo").to("direct:aggregate"); // With sync manual commit, this would throw a concurrent modification exception // It can be used in aggregator with completion timeout/interval for instance // WARN: records from one partition must be processed by one unique thread - from("direct:aggregate").routeId("aggregate").to(to) + from("direct:aggregate").routeId("aggregate").to(KafkaTestUtil.MOCK_RESULT) .aggregate() .constant(true) .completionTimeout(1) @@ -112,7 +105,7 @@ public void configure() { failCount++; } }); - from(from).routeId("bar").autoStartup(false).to(toBar); + from(uri).routeId("bar").autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } @@ -121,6 +114,8 @@ public void configure() { @Order(1) @Test void testLastRecordBeforeCommitHeader() { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); @@ -142,7 +137,7 @@ void testLastRecordBeforeCommitHeader() { @Order(2) @Test void kafkaManualCommit() throws Exception { - to.reset(); + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); // Second step: We shut down our route, we expect nothing will be recovered by our route context.getRouteController().stopRoute("foo"); @@ -161,10 +156,10 @@ void kafkaManualCommit() throws Exception { @Order(3) @Test void testResumeFromTheRightPoint() throws Exception { - to.reset(); + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); // Fourth step: We start again our route, since we have been committing the offsets from the first step, - // we will expect to consume from the latest committed offset e.g from offset 5 + // we will expect to consume from the latest committed offset i.e. from offset 5 context.getRouteController().startRoute("foo"); to.expectedMessageCount(3); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthIT.java index 77fb886caf5a3..487dfc5fef9fa 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthIT.java @@ -22,12 +22,18 @@ import java.util.concurrent.ExecutionException; import java.util.stream.StreamSupport; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaAdminUtil; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.camel.test.infra.kafka.services.ContainerLocalAuthKafkaService; +import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.config.SaslConfigs; import org.apache.kafka.common.header.internals.RecordHeader; @@ -37,9 +43,9 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,20 +55,27 @@ import static org.junit.jupiter.api.Assertions.fail; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class KafkaConsumerAuthIT extends BaseEmbeddedKafkaAuthTestSupport { +public class KafkaConsumerAuthIT { public static final String TOPIC = "test-auth-full"; - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAuthIT.class); + @Order(1) + @RegisterExtension + public static ContainerLocalAuthKafkaService service = new ContainerLocalAuthKafkaService("/kafka-jaas.config"); - @EndpointInject("mock:result") - private MockEndpoint to; + @Order(2) + @RegisterExtension + public static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + + protected static AdminClient kafkaAdminClient; + + private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAuthIT.class); private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach public void before() { - Properties props = getDefaultProperties(); + Properties props = KafkaTestUtil.getDefaultProperties(service); + props.put(SaslConfigs.SASL_JAAS_CONFIG, ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("camel", "camel-secret")); props.put("security.protocol", "SASL_PLAINTEXT"); @@ -77,6 +90,13 @@ public void before() { MockConsumerInterceptor.recordsCaptured.clear(); } + @BeforeEach + public void setKafkaAdminClient() { + if (kafkaAdminClient == null) { + kafkaAdminClient = KafkaAdminUtil.createAdminClient(service); + } + } + @AfterEach public void after() { if (producer != null) { @@ -86,7 +106,11 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); } - @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -96,14 +120,15 @@ public void configure() { = ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("camel", "camel-secret"); fromF("kafka:%s" - + "?groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "?brokers=%s&groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer&clientId=camel-kafka-auth-test" + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=%s" + "&saslMechanism=PLAIN&securityProtocol=SASL_PLAINTEXT&saslJaasConfig=%s", TOPIC, + service.getBootstrapServers(), "KafkaConsumerAuthIT", "org.apache.camel.component.kafka.MockConsumerInterceptor", simpleSaslJaasConfig) .process( exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("full-it").to(to); + .routeId("full-it").to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -113,6 +138,8 @@ public void configure() { @Order(1) @Test public void kafkaMessageIsConsumedByCamel() throws InterruptedException, ExecutionException { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + String propagatedHeaderKey = "PropagatedCustomHeader"; byte[] propagatedHeaderValue = "propagated header value".getBytes(); String skippedHeaderKey = "CamelSkippedHeader"; diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidIT.java index 902cad81ff46c..f8a72007dd166 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidIT.java @@ -20,13 +20,21 @@ import java.util.Map; import java.util.Properties; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaAdminUtil; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.camel.test.infra.kafka.services.ContainerLocalAuthKafkaService; +import org.apache.camel.test.infra.kafka.services.KafkaService; +import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; +import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.ConsumerGroupDescription; import org.apache.kafka.common.config.SaslConfigs; import org.junit.Assert; @@ -35,11 +43,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,23 +57,25 @@ @DisabledIfSystemProperty(named = "ci.env.name", matches = "github.com", disabledReason = "Flaky on Github CI") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class KafkaConsumerAuthInvalidIT extends BaseEmbeddedKafkaAuthTestSupport { +public class KafkaConsumerAuthInvalidIT { public static final String TOPIC = "test-auth-invalid-it"; - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAuthInvalidIT.class); + protected static AdminClient kafkaAdminClient; - @EndpointInject("mock:result") - private MockEndpoint to; + private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAuthInvalidIT.class); - @EndpointInject("mock:dlq") - private MockEndpoint dlq; + @Order(1) + @RegisterExtension + private static KafkaService service = KafkaServiceFactory.createSingletonService(); + @Order(2) + @RegisterExtension + private static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach public void before() { - Properties props = getDefaultProperties(); + Properties props = KafkaTestUtil.getDefaultProperties(service); props.put(SaslConfigs.SASL_JAAS_CONFIG, ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("camel", "camel-secret")); props.put("security.protocol", "SASL_PLAINTEXT"); @@ -79,6 +90,13 @@ public void before() { MockConsumerInterceptor.recordsCaptured.clear(); } + @BeforeEach + public void setKafkaAdminClient() { + if (kafkaAdminClient == null) { + kafkaAdminClient = KafkaTestUtil.createAdminClient(service); + } + } + @AfterEach public void after() { if (producer != null) { @@ -88,7 +106,11 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); } - @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -99,17 +121,17 @@ public void configure() { getCamelContext().adapt(ExtendedCamelContext.class) .setErrorHandlerFactory( - deadLetterChannel(dlq)); + deadLetterChannel("mock:dlq")); fromF("kafka:%s" - + "?groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "?brokers=%s&groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" + "&saslMechanism=PLAIN&securityProtocol=SASL_PLAINTEXT&saslJaasConfig=%s", TOPIC, - "KafkaConsumerAuthInvalidIT", simpleSaslJaasConfig) + service.getBootstrapServers(), "KafkaConsumerAuthInvalidIT", simpleSaslJaasConfig) .process( exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("should-no-work").to(to); + .routeId("should-no-work").to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -118,6 +140,9 @@ public void configure() { @Timeout(30) @Test public void kafkaMessageIsConsumedByCamel() throws InterruptedException { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + MockEndpoint dlq = contextExtension.getMockEndpoint("mock:dlq"); + dlq.expectedMessageCount(1); dlq.assertIsSatisfied(3000); @@ -125,7 +150,8 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { to.assertIsSatisfied(3000); final Map allGroups - = assertDoesNotThrow(() -> getConsumerGroupInfo("KafkaConsumerAuthInvalidIT")); + = assertDoesNotThrow(() -> KafkaAdminUtil.getConsumerGroupInfo("KafkaConsumerAuthInvalidIT", + kafkaAdminClient)); final ConsumerGroupDescription groupInfo = allGroups.get("KafkaConsumerAuthInvalidIT"); Assert.assertEquals("There should be no members in this group", 0, groupInfo.members().size()); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidWithReconnectIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidWithReconnectIT.java index 81521b4785e5f..76789b5307629 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidWithReconnectIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthInvalidWithReconnectIT.java @@ -16,18 +16,18 @@ */ package org.apache.camel.component.kafka.integration; -import java.util.Collections; import java.util.Map; import java.util.Properties; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.apache.camel.CamelContext; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.MockConsumerInterceptor; -import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.kafka.integration.common.KafkaAdminUtil; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.camel.test.infra.kafka.services.ContainerLocalAuthKafkaService; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.ConsumerGroupDescription; @@ -40,8 +40,8 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,19 +49,15 @@ import static org.junit.jupiter.api.Assertions.fail; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class KafkaConsumerAuthInvalidWithReconnectIT extends AbstractKafkaTestSupport { +public class KafkaConsumerAuthInvalidWithReconnectIT { public static final String TOPIC = "test-auth-invalid-with-reconnect"; private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAuthInvalidWithReconnectIT.class); private static ContainerLocalAuthKafkaService service; - @EndpointInject("mock:result") - private MockEndpoint to; - - @EndpointInject("mock:dlq") - private MockEndpoint dlq; + @RegisterExtension + private static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); private org.apache.kafka.clients.producer.KafkaProducer producer; @@ -73,16 +69,7 @@ public class KafkaConsumerAuthInvalidWithReconnectIT extends AbstractKafkaTestSu @BeforeAll public static void beforeClass() { service.initialize(); - AbstractKafkaTestSupport.setServiceProperties(service); - } - - protected Properties getDefaultProperties() { - return getDefaultProperties(service); - } - - @Override - protected CamelContext createCamelContext() throws Exception { - return createCamelContextFromService(service); + KafkaTestUtil.setServiceProperties(service); } protected static String getBootstrapServers() { @@ -92,7 +79,7 @@ protected static String getBootstrapServers() { @BeforeEach public void before() { - Properties props = getDefaultProperties(); + Properties props = KafkaTestUtil.getDefaultProperties(service); props.put(SaslConfigs.SASL_JAAS_CONFIG, ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("camel", "camel-secret")); props.put("security.protocol", "SASL_PLAINTEXT"); @@ -114,7 +101,11 @@ public void after() { } } - @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -123,31 +114,28 @@ public void configure() { final String simpleSaslJaasConfig = ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("camel", "camel-secret"); - fromF("kafka:%s" - + "?groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + fromF("kafka:%s?brokers=%s" + + "&groupId=%s&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&pollOnError=RECONNECT" + "&saslMechanism=PLAIN&securityProtocol=SASL_PLAINTEXT&saslJaasConfig=%s", TOPIC, + service.getBootstrapServers(), "KafkaConsumerAuthInvalidWithReconnectIT", simpleSaslJaasConfig) .process( exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("full-it").to(to); + .routeId("full-it").to(KafkaTestUtil.MOCK_DLQ); } }; } - private Map getConsumerGroupInfo(AdminClient adminClient, String groupId) - throws InterruptedException, ExecutionException, TimeoutException { - return adminClient.describeConsumerGroups(Collections.singletonList(groupId)).all().get(30, TimeUnit.SECONDS); - } - @Test @Order(1) void testIsDisconnected() { - AdminClient adminClient = BaseEmbeddedKafkaAuthTestSupport.createAuthAdminClient(service); + AdminClient adminClient = KafkaAdminUtil.createAuthAdminClient(service); final Map allGroups - = assertDoesNotThrow(() -> getConsumerGroupInfo(adminClient, "KafkaConsumerAuthInvalidWithReconnectIT")); + = assertDoesNotThrow( + () -> KafkaAdminUtil.getConsumerGroupInfo("KafkaConsumerAuthInvalidWithReconnectIT", adminClient)); final ConsumerGroupDescription groupInfo = allGroups.get("KafkaConsumerAuthInvalidWithReconnectIT"); Assert.assertEquals("There should be no members in this group", 0, groupInfo.members().size()); @@ -166,14 +154,15 @@ void testReconnect() { service.initialize(); - AdminClient adminClient = BaseEmbeddedKafkaAuthTestSupport.createAuthAdminClient(service); + AdminClient adminClient = KafkaAdminUtil.createAuthAdminClient(service); Awaitility.await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> assertIsConnected(adminClient)); adminClient.close(); } private void assertIsConnected(AdminClient adminClient) { final Map allGroups - = assertDoesNotThrow(() -> getConsumerGroupInfo(adminClient, "KafkaConsumerAuthInvalidWithReconnectIT")); + = assertDoesNotThrow( + () -> KafkaAdminUtil.getConsumerGroupInfo("KafkaConsumerAuthInvalidWithReconnectIT", adminClient)); Assert.assertTrue("There should be at least one group named KafkaConsumerAuthInvalidWithReconnectIT", allGroups.size() >= 1); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthManualTest.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthManualTest.java index 1adf75632582c..f45b5b9687f39 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthManualTest.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAuthManualTest.java @@ -19,10 +19,14 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.camel.test.infra.kafka.services.ContainerLocalAuthKafkaService; import org.apache.kafka.common.config.SaslConfigs; import org.awaitility.Awaitility; @@ -36,6 +40,7 @@ import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.EnabledIfSystemProperties; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.jupiter.api.Assertions.fail; @@ -47,8 +52,8 @@ @EnabledIfSystemProperty(named = "kafka.manual.test.username", matches = ".*"), @EnabledIfSystemProperty(named = "kafka.manual.test.password", matches = ".*"), }) -public class KafkaConsumerAuthManualTest extends AbstractKafkaTestSupport { - public static final String TOPIC = System.getProperty("kafka.manual.test.topic"); +public class KafkaConsumerAuthManualTest { + private static final String TOPIC = System.getProperty("kafka.manual.test.topic"); private static final String BOOTSTRAP_SERVERS = System.getProperty("bootstrapServers"); private static final String USERNAME = System.getProperty("kafka.manual.test.username"); private static final String PASSWORD = System.getProperty("kafka.manual.test.password"); @@ -56,15 +61,13 @@ public class KafkaConsumerAuthManualTest extends AbstractKafkaTestSupport { private static final String SASL_MECHANISM = System.getProperty("kafka.manual.test.sasl.mechanism", "PLAIN"); private static final int MESSAGE_COUNT = Integer.valueOf(System.getProperty("kafka.manual.test.message.count", "5")); - @EndpointInject("mock:result") - private MockEndpoint to; - + @RegisterExtension + private static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + private static volatile int receivedMessages; private org.apache.kafka.clients.producer.KafkaProducer producer; - private volatile int receivedMessages; - @Override protected Properties getDefaultProperties() { - Properties properties = AbstractKafkaTestSupport.getDefaultProperties(BOOTSTRAP_SERVERS); + Properties properties = KafkaTestUtil.getDefaultProperties(BOOTSTRAP_SERVERS); properties.put(SaslConfigs.SASL_JAAS_CONFIG, ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig(USERNAME, PASSWORD)); @@ -94,7 +97,11 @@ public void after() { // clean all test topics } - @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -109,7 +116,7 @@ public void configure() { + "&saslMechanism=%s&securityProtocol=%s&saslJaasConfig=%s", TOPIC, BOOTSTRAP_SERVERS, "KafkaConsumerAuthManualTest", SASL_MECHANISM, SECURITY_PROTOCOL, simpleSaslJaasConfig) .process(e -> receivedMessages++) - .routeId("full-it").to(to); + .routeId("full-it").to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -117,6 +124,7 @@ public void configure() { @DisplayName("Tests that Camel can adequately connect and consume from an authenticated remote Kafka instance") @Test public void kafkaMessageIsConsumedByCamel() throws InterruptedException { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAutoInstResumeRouteStrategyIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAutoInstResumeRouteStrategyIT.java index 5710bc670ce08..ccc141f27a32f 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAutoInstResumeRouteStrategyIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerAutoInstResumeRouteStrategyIT.java @@ -20,10 +20,10 @@ import java.util.Collections; import java.util.Properties; -import org.apache.camel.EndpointInject; import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.consumer.support.resume.KafkaResumable; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.processor.resume.TransientResumeStrategy; import org.apache.camel.processor.resume.kafka.KafkaResumeStrategyConfigurationBuilder; @@ -33,16 +33,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class KafkaConsumerAutoInstResumeRouteStrategyIT extends BaseEmbeddedKafkaTestSupport { - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerAutoInstResumeRouteStrategyIT.class); private static final String TOPIC = "resumable-route-auto"; - @EndpointInject("mock:result") - private MockEndpoint result; - public static KafkaResumeStrategyConfigurationBuilder getDefaultKafkaResumeStrategyConfigurationBuilder() { return KafkaResumeStrategyConfigurationBuilder.newBuilder() .withBootstrapServers(service.getBootstrapServers()) @@ -58,7 +52,7 @@ public static KafkaResumeStrategyConfigurationBuilder getDefaultKafkaResumeStrat @BeforeEach public void before() { - Properties props = getDefaultProperties(); + Properties props = KafkaTestUtil.getDefaultProperties(service); KafkaProducer producer = new KafkaProducer<>(props); for (int i = 0; i < 10; i++) { @@ -66,16 +60,10 @@ public void before() { } } - @Override - protected void doPreSetup() throws Exception { - super.doPreSetup(); - - } - @Test @Timeout(value = 30) public void testOffsetIsBeingChecked() throws InterruptedException { - MockEndpoint mock = getMockEndpoint("mock:result"); + MockEndpoint mock = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); mock.expectedMessageCount(10); mock.assertIsSatisfied(); @@ -86,7 +74,7 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)); } - public void process(Exchange exchange) { + private void process(Exchange exchange) { exchange.getMessage().setHeader(Exchange.OFFSET, KafkaResumable.of(exchange)); } @@ -106,7 +94,7 @@ public void configure() { fromF("kafka:%s?groupId=%s_GROUP&autoCommitIntervalMs=1000", "resumable-route-auto-offsets", "resumable-route-auto-offsets") - .to("mock:result"); + .to(KafkaTestUtil.MOCK_RESULT); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerBatchSizeIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerBatchSizeIT.java index 222981191f97b..388a8b22d17f0 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerBatchSizeIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerBatchSizeIT.java @@ -19,9 +19,9 @@ import java.util.Collections; import java.util.Properties; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterEach; @@ -32,12 +32,6 @@ public class KafkaConsumerBatchSizeIT extends BaseEmbeddedKafkaTestSupport { public static final String TOPIC = "test-batch"; - @EndpointInject("kafka:" + TOPIC + "?autoOffsetReset=earliest&autoCommitEnable=false&consumersCount=1") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach @@ -55,18 +49,20 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)); } - @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to(to).setId("First"); + fromF("kafka:%s?brokers=%s&autoOffsetReset=earliest&autoCommitEnable=false&consumersCount=1", + TOPIC, service.getBootstrapServers()) + .routeId("foo").to(KafkaTestUtil.MOCK_RESULT).setId("First"); } }; } @Test public void kafkaMessagesIsConsumedByCamel() throws Exception { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); // First 2 must not be committed since batch size is 3 to.expectedBodiesReceivedInAnyOrder("m1", "m2"); @@ -82,7 +78,9 @@ public void kafkaMessagesIsConsumedByCamel() throws Exception { to.expectedBodiesReceivedInAnyOrder("m3", "m4", "m5", "m6", "m7", "m8", "m9", "m10"); - // Restart endpoint, + // Restart endpoint + CamelContext context = contextExtension.getContext(); + context.getRouteController().stopRoute("foo"); context.getRouteController().startRoute("foo"); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerFullIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerFullIT.java index a5756b4a4e8d5..598398462a7f7 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerFullIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerFullIT.java @@ -22,15 +22,16 @@ import java.util.stream.StreamSupport; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.KafkaEndpoint; import org.apache.camel.component.kafka.MockConsumerInterceptor; import org.apache.camel.component.kafka.SeekPolicy; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.kafka.serde.DefaultKafkaHeaderDeserializer; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.RouteFixture; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; import org.junit.jupiter.api.AfterEach; @@ -43,9 +44,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.camel.test.junit5.TestSupport.assertIsInstanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -55,19 +56,14 @@ public class KafkaConsumerFullIT extends BaseEmbeddedKafkaTestSupport { private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerFullIT.class); - @BindToRegistry("myHeaderDeserializer") - private MyKafkaHeaderDeserializer deserializer = new MyKafkaHeaderDeserializer(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerFullIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; + private static final String FROM_URI = "kafka:" + TOPIC + + "?groupId=KafkaConsumerFullIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" + + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor"; private org.apache.kafka.clients.producer.KafkaProducer producer; + @BindToRegistry("myHeaderDeserializer") + private MyKafkaHeaderDeserializer bean = new MyKafkaHeaderDeserializer(); @BeforeEach public void before() { @@ -85,14 +81,18 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); } - @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { - @Override public void configure() { - from(from).process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("full-it").to(to); + from(FROM_URI) + .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) + .routeId("full-it").to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -103,6 +103,9 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { String propagatedHeaderKey = "PropagatedCustomHeader"; byte[] propagatedHeaderValue = "propagated header value".getBytes(); String skippedHeaderKey = "CamelSkippedHeader"; + + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); // The LAST_RECORD_BEFORE_COMMIT header should not be configured on any @@ -133,6 +136,8 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { public void kafkaRecordSpecificHeadersAreNotOverwritten() throws InterruptedException { String propagatedHeaderKey = KafkaConstants.TOPIC; byte[] propagatedHeaderValue = "propagated incorrect topic".getBytes(); + + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); to.expectedHeaderReceived(KafkaConstants.TOPIC, TOPIC); ProducerRecord data = new ProducerRecord<>(TOPIC, "1", "message"); @@ -149,6 +154,7 @@ public void kafkaRecordSpecificHeadersAreNotOverwritten() throws InterruptedExce @Test @Order(1) public void kafkaMessageIsConsumedByCamelSeekedToBeginning() throws Exception { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); for (int k = 0; k < 5; k++) { @@ -164,10 +170,11 @@ public void kafkaMessageIsConsumedByCamelSeekedToBeginning() throws Exception { to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); - // Restart endpoint, + // Restart endpoint + CamelContext context = contextExtension.getContext(); context.getRouteController().stopRoute("full-it"); - KafkaEndpoint kafkaEndpoint = (KafkaEndpoint) from; + KafkaEndpoint kafkaEndpoint = (KafkaEndpoint) context.getEndpoint(FROM_URI); kafkaEndpoint.getConfiguration().setSeekTo(SeekPolicy.BEGINNING); context.getRouteController().startRoute("full-it"); @@ -179,6 +186,8 @@ public void kafkaMessageIsConsumedByCamelSeekedToBeginning() throws Exception { @Order(4) @Test public void kafkaMessageIsConsumedByCamelSeekedToEnd() throws Exception { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); for (int k = 0; k < 5; k++) { @@ -192,10 +201,11 @@ public void kafkaMessageIsConsumedByCamelSeekedToEnd() throws Exception { to.expectedMessageCount(0); - // Restart endpoint, + // Restart endpoint + CamelContext context = contextExtension.getContext(); context.getRouteController().stopRoute("full-it"); - KafkaEndpoint kafkaEndpoint = (KafkaEndpoint) from; + KafkaEndpoint kafkaEndpoint = (KafkaEndpoint) context.getEndpoint(FROM_URI); kafkaEndpoint.getConfiguration().setSeekTo(SeekPolicy.END); context.getRouteController().startRoute("full-it"); @@ -206,9 +216,11 @@ public void kafkaMessageIsConsumedByCamelSeekedToEnd() throws Exception { @Order(5) @Test public void headerDeserializerCouldBeOverridden() { + CamelContext context = contextExtension.getContext(); + KafkaEndpoint kafkaEndpoint = context.getEndpoint("kafka:random_topic?headerDeserializer=#myHeaderDeserializer", KafkaEndpoint.class); - assertIsInstanceOf(MyKafkaHeaderDeserializer.class, kafkaEndpoint.getConfiguration().getHeaderDeserializer()); + assertInstanceOf(MyKafkaHeaderDeserializer.class, kafkaEndpoint.getConfiguration().getHeaderDeserializer()); } private static class MyKafkaHeaderDeserializer extends DefaultKafkaHeaderDeserializer { diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerHealthCheckIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerHealthCheckIT.java deleted file mode 100644 index e6fbcd24d6b6e..0000000000000 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerHealthCheckIT.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.kafka.integration; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.Properties; -import java.util.concurrent.TimeUnit; -import java.util.stream.StreamSupport; - -import org.apache.camel.BindToRegistry; -import org.apache.camel.CamelContext; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.kafka.KafkaComponent; -import org.apache.camel.component.kafka.KafkaConstants; -import org.apache.camel.component.kafka.MockConsumerInterceptor; -import org.apache.camel.component.kafka.serde.DefaultKafkaHeaderDeserializer; -import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.health.HealthCheck; -import org.apache.camel.health.HealthCheckHelper; -import org.apache.camel.health.HealthCheckRegistry; -import org.apache.camel.impl.health.DefaultHealthCheckRegistry; -import org.apache.camel.test.infra.kafka.services.KafkaService; -import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.internals.RecordHeader; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.testcontainers.shaded.org.awaitility.Awaitility.await; - -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) - -public class KafkaConsumerHealthCheckIT extends CamelTestSupport { - public static final String TOPIC = "test-health"; - - public static KafkaService service = KafkaServiceFactory.createService(); - - protected static AdminClient kafkaAdminClient; - - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerHealthCheckIT.class); - - @BindToRegistry("myHeaderDeserializer") - private MyKafkaHeaderDeserializer deserializer = new MyKafkaHeaderDeserializer(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - @EndpointInject("mock:result") - private MockEndpoint to; - - private org.apache.kafka.clients.producer.KafkaProducer producer; - - @BeforeEach - public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); - producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); - MockConsumerInterceptor.recordsCaptured.clear(); - } - - @BeforeAll - public static void beforeClass() { - service.initialize(); - - LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); - System.setProperty("bootstrapServers", service.getBootstrapServers()); - System.setProperty("brokers", service.getBootstrapServers()); - } - - @AfterAll - public static void afterClass() { - service.shutdown(); - } - - @BeforeEach - public void setKafkaAdminClient() { - if (kafkaAdminClient == null) { - kafkaAdminClient = AbstractKafkaTestSupport.createAdminClient(service); - } - } - - @AfterEach - public void after() { - if (producer != null) { - producer.close(); - } - // clean all test topics - kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); - } - - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext context = super.createCamelContext(); - context.getPropertiesComponent().setLocation("ref:prop"); - - KafkaComponent kafka = new KafkaComponent(context); - kafka.init(); - kafka.getConfiguration().setBrokers(service.getBootstrapServers()); - context.addComponent("kafka", kafka); - - // install health check manually (yes a bit cumbersome) - HealthCheckRegistry registry = new DefaultHealthCheckRegistry(); - registry.setCamelContext(context); - Object hc = registry.resolveById("context"); - registry.register(hc); - hc = registry.resolveById("routes"); - registry.register(hc); - hc = registry.resolveById("consumers"); - registry.register(hc); - context.setExtension(HealthCheckRegistry.class, registry); - - return context; - } - - @Override - protected RouteBuilder createRouteBuilder() { - return new RouteBuilder() { - - @Override - public void configure() { - from(from) - .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("test-health-it").to(to); - } - }; - } - - @Order(1) - @Test - public void kafkaConsumerHealthCheck() throws InterruptedException { - // health-check liveness should be UP - Collection res = HealthCheckHelper.invokeLiveness(context); - boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); - Assertions.assertTrue(up, "liveness check"); - - // health-check readiness should be ready - await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { - Collection res2 = HealthCheckHelper.invokeReadiness(context); - boolean up2 = res2.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); - Assertions.assertTrue(up2, "readiness check"); - }); - - String propagatedHeaderKey = "PropagatedCustomHeader"; - byte[] propagatedHeaderValue = "propagated header value".getBytes(); - String skippedHeaderKey = "CamelSkippedHeader"; - to.expectedMessageCount(5); - to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); - // The LAST_RECORD_BEFORE_COMMIT header should not be configured on any - // exchange because autoCommitEnable=true - to.expectedHeaderValuesReceivedInAnyOrder(KafkaConstants.LAST_RECORD_BEFORE_COMMIT, null, null, null, null, null); - to.expectedHeaderReceived(propagatedHeaderKey, propagatedHeaderValue); - - for (int k = 0; k < 5; k++) { - String msg = "message-" + k; - ProducerRecord data = new ProducerRecord<>(TOPIC, "1", msg); - data.headers().add(new RecordHeader("CamelSkippedHeader", "skipped header value".getBytes())); - data.headers().add(new RecordHeader(propagatedHeaderKey, propagatedHeaderValue)); - producer.send(data); - } - - to.assertIsSatisfied(3000); - - assertEquals(5, MockConsumerInterceptor.recordsCaptured.stream() - .flatMap(i -> StreamSupport.stream(i.records(TOPIC).spliterator(), false)).count()); - - Map headers = to.getExchanges().get(0).getIn().getHeaders(); - assertFalse(headers.containsKey(skippedHeaderKey), "Should not receive skipped header"); - assertTrue(headers.containsKey(propagatedHeaderKey), "Should receive propagated header"); - - // and shutdown kafka which will make readiness report as DOWN - service.shutdown(); - - // health-check liveness should be UP - res = HealthCheckHelper.invokeLiveness(context); - up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); - Assertions.assertTrue(up, "liveness check"); - // but health-check readiness should NOT be ready - await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { - Collection res2 = HealthCheckHelper.invoke(context); - Optional down - = res2.stream().filter(r -> r.getState().equals(HealthCheck.State.DOWN)).findFirst(); - Assertions.assertTrue(down.isPresent()); - String msg = down.get().getMessage().get(); - Assertions.assertTrue(msg.contains("KafkaConsumer is not ready")); - Map map = down.get().getDetails(); - Assertions.assertEquals(TOPIC, map.get("topic")); - Assertions.assertEquals("test-health-it", map.get("route.id")); - }); - } - - private static class MyKafkaHeaderDeserializer extends DefaultKafkaHeaderDeserializer { - } -} diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentIT.java index 682774dc765ee..5874a59b3cf6a 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentIT.java @@ -19,9 +19,8 @@ import java.util.Arrays; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.processor.idempotent.kafka.KafkaIdempotentRepository; import org.junit.jupiter.api.AfterEach; @@ -33,27 +32,15 @@ import static org.apache.camel.component.kafka.serde.KafkaSerdeHelper.numericHeader; @DisabledIfSystemProperty(named = "enable.kafka.consumer.idempotency.tests", matches = "false") -class KafkaConsumerIdempotentIT extends KafkaConsumerIdempotentTestSupport { +public class KafkaConsumerIdempotentIT extends KafkaConsumerIdempotentTestSupport { public static final String TOPIC = "idempt"; - @BindToRegistry("kafkaIdempotentRepository") - private KafkaIdempotentRepository kafkaIdempotentRepository - = new KafkaIdempotentRepository("TEST_IDEMPOTENT", getBootstrapServers()); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerIdempotentIT&autoOffsetReset=earliest" - + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" - + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - private int size = 200; + @BindToRegistry("kafkaIdempotentRepository") + private KafkaIdempotentRepository testIdempotent = new KafkaIdempotentRepository("TEST_IDEMPOTENT", getBootstrapServers()); + @BeforeEach public void before() { kafkaAdminClient.deleteTopics(Arrays.asList(TOPIC, "TEST_IDEMPOTENT")).all(); @@ -65,17 +52,20 @@ public void after() { kafkaAdminClient.deleteTopics(Arrays.asList(TOPIC, "TEST_IDEMPOTENT")).all(); } - @Override protected RouteBuilder createRouteBuilder() { - return new RouteBuilder() { @Override public void configure() { - from(from).routeId("foo") + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerIdempotentIT&autoOffsetReset=earliest" + + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" + + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor").routeId("foo") .idempotentConsumer(numericHeader("id")) .idempotentRepository("kafkaIdempotentRepository") - .to(to); + .to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -83,6 +73,8 @@ public void configure() { @Test @DisplayName("Numeric headers is consumable when using idempotent (CAMEL-16914)") void kafkaIdempotentMessageIsConsumedByCamel() { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + doRun(to, size); } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithCustomSerializerIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithCustomSerializerIT.java index f50f90c2ba7a1..66270b49ecca7 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithCustomSerializerIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithCustomSerializerIT.java @@ -19,40 +19,26 @@ import java.util.Arrays; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.processor.idempotent.kafka.KafkaIdempotentRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class KafkaConsumerIdempotentWithCustomSerializerIT extends KafkaConsumerIdempotentTestSupport { +public class KafkaConsumerIdempotentWithCustomSerializerIT extends KafkaConsumerIdempotentTestSupport { public static final String TOPIC = "idempt2"; + private int size = 200; + @BindToRegistry("kafkaIdempotentRepository") private KafkaIdempotentRepository kafkaIdempotentRepository = new KafkaIdempotentRepository("TEST_IDEMPOTENT", getBootstrapServers()); - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerIdempotentWithCustomSerializerIT&autoOffsetReset=earliest" - + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&headerDeserializer=#class:org.apache.camel.component.kafka.integration.CustomHeaderDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" - + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - - private int size = 200; - @BeforeEach public void before() { - kafkaIdempotentRepository.clear(); kafkaAdminClient.deleteTopics(Arrays.asList(TOPIC, "TEST_IDEMPOTENT")).all(); doSend(size, TOPIC); } @@ -62,22 +48,29 @@ public void after() { kafkaAdminClient.deleteTopics(Arrays.asList(TOPIC, "TEST_IDEMPOTENT")).all(); } - @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from(from).routeId("foo") + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerIdempotentWithCustomSerializerIT&autoOffsetReset=earliest" + + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&headerDeserializer=#class:org.apache.camel.component.kafka.integration.CustomHeaderDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" + + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor").routeId("foo") .idempotentConsumer(header("id")) .idempotentRepository("kafkaIdempotentRepository") - .to(to); + .to(KafkaTestUtil.MOCK_RESULT); } }; } @Test void kafkaMessageIsConsumedByCamel() { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + doRun(to, size); } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithProcessorIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithProcessorIT.java index 2f270b99fabf9..467996bb2e6db 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithProcessorIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerIdempotentWithProcessorIT.java @@ -20,38 +20,24 @@ import java.util.Arrays; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.processor.idempotent.kafka.KafkaIdempotentRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class KafkaConsumerIdempotentWithProcessorIT extends KafkaConsumerIdempotentTestSupport { +public class KafkaConsumerIdempotentWithProcessorIT extends KafkaConsumerIdempotentTestSupport { public static final String TOPIC = "testidemp3"; + private int size = 200; @BindToRegistry("kafkaIdempotentRepository") private KafkaIdempotentRepository kafkaIdempotentRepository = new KafkaIdempotentRepository("TEST_IDEMPOTENT", getBootstrapServers()); - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerIdempotentWithProcessorIT&autoOffsetReset=earliest" - + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" - + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("mock:resulti") - private MockEndpoint to; - - private int size = 200; - @BeforeEach public void before() { - kafkaIdempotentRepository.clear(); kafkaAdminClient.deleteTopics(Arrays.asList(TOPIC, "TEST_IDEMPOTENT")).all(); doSend(size, TOPIC); } @@ -64,12 +50,17 @@ public void after() { @Override protected RouteBuilder createRouteBuilder() { - return new RouteBuilder() { @Override public void configure() { - from(from).routeId("idemp-with-prop") + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerIdempotentWithProcessorIT&autoOffsetReset=earliest" + + "&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true" + + "&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") + .routeId("idemp-with-prop") .process(exchange -> { byte[] id = exchange.getIn().getHeader("id", byte[].class); @@ -79,13 +70,15 @@ public void configure() { }) .idempotentConsumer(header("id")) .idempotentRepository("kafkaIdempotentRepository") - .to(to); + .to(KafkaTestUtil.MOCK_RESULT); } }; } @Test void kafkaMessageIsConsumedByCamel() { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + doRun(to, size); } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerLastRecordHeaderIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerLastRecordHeaderIT.java index 68e098729d55e..19b5f950c143b 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerLastRecordHeaderIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerLastRecordHeaderIT.java @@ -20,10 +20,10 @@ import java.util.List; import java.util.Properties; -import org.apache.camel.EndpointInject; import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterEach; @@ -39,9 +39,6 @@ public class KafkaConsumerLastRecordHeaderIT extends BaseEmbeddedKafkaTestSuppor private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerLastRecordHeaderIT.class); private static final String TOPIC = "last-record"; - @EndpointInject("mock:result") - private MockEndpoint result; - private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach @@ -65,6 +62,7 @@ public void after() { */ @Test public void shouldStartFromBeginningWithEmptyOffsetRepository() throws InterruptedException { + MockEndpoint result = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); result.expectedMessageCount(5); result.expectedBodiesReceived("message-0", "message-1", "message-2", "message-3", "message-4"); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerRebalanceIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerRebalanceIT.java index b56211ff66d05..0067981b6d42e 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerRebalanceIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerRebalanceIT.java @@ -21,9 +21,8 @@ import java.util.concurrent.TimeUnit; import org.apache.camel.BindToRegistry; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.spi.StateRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -35,18 +34,10 @@ public class KafkaConsumerRebalanceIT extends BaseEmbeddedKafkaTestSupport { private static final String TOPIC = "offset-rebalance"; - @EndpointInject("mock:result") - private MockEndpoint result; + private CountDownLatch messagesLatch = new CountDownLatch(1); @BindToRegistry("offset") - private OffsetStateRepository stateRepository; - private CountDownLatch messagesLatch; - - @Override - protected void doPreSetup() { - messagesLatch = new CountDownLatch(1); - stateRepository = new OffsetStateRepository(messagesLatch); - } + private OffsetStateRepository offsetStateRepository = new OffsetStateRepository(messagesLatch); @Test public void offsetGetStateMustHaveBeenCalledTwice() throws Exception { @@ -62,14 +53,14 @@ public void after() { kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)); } - @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { from("kafka:" + TOPIC + "?groupId=" + TOPIC + "_GROUP" + "&autoCommitIntervalMs=1000" + "&autoOffsetReset=latest" + "&consumersCount=1" - + "&offsetRepository=#offset").routeId("consumer-rebalance-route").to("mock:result"); + + "&offsetRepository=#offset").routeId("consumer-rebalance-route") + .to(KafkaTestUtil.MOCK_RESULT); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerStopIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerStopIT.java index fe961683dbe82..36c93f5b699fa 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerStopIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerStopIT.java @@ -21,19 +21,17 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConsumer; import org.apache.camel.component.kafka.KafkaFetchRecords; import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.util.ReflectionUtils; @@ -47,22 +45,12 @@ * This IT is based on {@link KafkaConsumerFullIT} */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class KafkaConsumerStopIT extends BaseEmbeddedKafkaTestSupport { public static final String TOPIC = "test-full"; private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerStopIT.class); - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerFullIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach @@ -87,15 +75,21 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from).process(exchange -> LOG.trace("Captured on the processor: {}", - exchange.getMessage().getBody())) - .routeId("full-it").to(to); + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerFullIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" + + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") + .process(exchange -> LOG.trace("Captured on the processor: {}", + exchange.getMessage().getBody())) + .routeId("full-it").to(KafkaTestUtil.MOCK_RESULT); } }; } @Test public void kafkaClientConsumerClosedWhenKafkaRouteStopped() throws Exception { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + // given: kafka consumer route to.expectedBodiesReceivedInAnyOrder("message"); ProducerRecord data = new ProducerRecord<>(TOPIC, "1", "message"); @@ -106,7 +100,7 @@ public void kafkaClientConsumerClosedWhenKafkaRouteStopped() throws Exception { org.apache.kafka.clients.consumer.KafkaConsumer kafkaClientConsumer = getKafkaClientConsumer(); // when: kafka consumer route stopped - context.getRouteController().stopRoute("full-it"); + contextExtension.getContext().getRouteController().stopRoute("full-it"); // then: org.apache.kafka.clients.consumer.KafkaConsumer closed await().atMost(10, TimeUnit.SECONDS) @@ -115,7 +109,7 @@ public void kafkaClientConsumerClosedWhenKafkaRouteStopped() throws Exception { } private org.apache.kafka.clients.consumer.KafkaConsumer getKafkaClientConsumer() throws Exception { - KafkaConsumer kafkaConsumer = (KafkaConsumer) context.getRoute("full-it").getConsumer(); + KafkaConsumer kafkaConsumer = (KafkaConsumer) contextExtension.getContext().getRoute("full-it").getConsumer(); Try tasksTry = ReflectionUtils.tryToReadFieldValue(KafkaConsumer.class, "tasks", kafkaConsumer); KafkaFetchRecords kafkaFetchRecords = ((List) tasksTry.get()).get(0); Try kafkaClientConsumerTry diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerTopicIsPatternIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerTopicIsPatternIT.java index bbae0f7491098..c9868ee27fd68 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerTopicIsPatternIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaConsumerTopicIsPatternIT.java @@ -20,11 +20,10 @@ import java.util.Properties; import java.util.stream.StreamSupport; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterEach; @@ -38,14 +37,6 @@ public class KafkaConsumerTopicIsPatternIT extends BaseEmbeddedKafkaTestSupport public static final String TOPIC = "vess123d"; public static final String TOPIC_PATTERN = "v.*d"; - @EndpointInject("kafka:" + TOPIC_PATTERN - + "?topicIsPattern=true&groupId=KafkaConsumerTopicIsPatternIT&autoOffsetReset=earliest" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor&metadataMaxAgeMs=1000") - private Endpoint from; - - @EndpointInject("mock:result") - private MockEndpoint to; - private org.apache.kafka.clients.producer.KafkaProducer producer; @BeforeEach @@ -71,13 +62,18 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from).to(to); + from("kafka:" + TOPIC_PATTERN + + "?topicIsPattern=true&groupId=KafkaConsumerTopicIsPatternIT&autoOffsetReset=earliest" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor&metadataMaxAgeMs=1000") + .to(KafkaTestUtil.MOCK_RESULT); } }; } @Test public void kafkaTopicIsPattern() throws Exception { + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); to.allMessages().header(KafkaConstants.TOPIC).isEqualTo(TOPIC); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaProducerFullIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaProducerFullIT.java index 3906f30ef8c14..504ae4e4bef94 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaProducerFullIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaProducerFullIT.java @@ -30,10 +30,7 @@ import java.util.stream.StreamSupport; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.Exchange; -import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; @@ -51,16 +48,24 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.apache.camel.test.junit5.TestSupport.assertIsInstanceOf; +import static org.apache.camel.component.kafka.integration.common.TestProducerUtil.sendMessagesInRoute; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class KafkaProducerFullIT extends BaseEmbeddedKafkaTestSupport { + public static final String DIRECT_START_STRINGS_URI = "direct:startStrings"; + public static final String DIRECT_START_STRINGS_2_URI = "direct:startStrings2"; + public static final String DIRECT_START_BYTES_URI = "direct:startBytes"; + public static final String DIRECT_START_TRACED_URI = "direct:startTraced"; + public static final String DIRECT_PROPAGATED_HEADERS_URI = "direct:propagatedHeaders"; + public static final String DIRECT_NO_RECORD_SPECIFIC_HEADERS_URI = "direct:noRecordSpecificHeaders"; private static final String TOPIC_STRINGS = "test"; private static final String TOPIC_INTERCEPTED = "test"; private static final String TOPIC_STRINGS_IN_HEADER = "testHeader"; @@ -69,57 +74,21 @@ public class KafkaProducerFullIT extends BaseEmbeddedKafkaTestSupport { private static final String GROUP_BYTES = "groupStrings"; private static final String TOPIC_PROPAGATED_HEADERS = "testPropagatedHeaders"; private static final String TOPIC_NO_RECORD_SPECIFIC_HEADERS = "noRecordSpecificHeaders"; - + private static final String KAFKA_ACK_MOCK = "mock:kafkaAck"; private static KafkaConsumer stringsConsumerConn; private static KafkaConsumer bytesConsumerConn; - - @EndpointInject("kafka:" + TOPIC_STRINGS + "?requestRequiredAcks=-1") - private Endpoint toStrings; - - @EndpointInject("kafka:" + TOPIC_STRINGS + "?requestRequiredAcks=-1&partitionKey=0") - private Endpoint toStrings2; - - @EndpointInject("kafka:" + TOPIC_INTERCEPTED + "?requestRequiredAcks=-1" - + "&interceptorClasses=org.apache.camel.component.kafka.MockProducerInterceptor") - private Endpoint toStringsWithInterceptor; - - @EndpointInject("mock:kafkaAck") - private MockEndpoint mockEndpoint; - - @EndpointInject("kafka:" + TOPIC_BYTES + "?requestRequiredAcks=-1" - + "&valueSerializer=org.apache.kafka.common.serialization.ByteArraySerializer&" - + "keySerializer=org.apache.kafka.common.serialization.ByteArraySerializer") - private Endpoint toBytes; - - @EndpointInject("kafka:" + TOPIC_PROPAGATED_HEADERS + "?requestRequiredAcks=-1") - private Endpoint toPropagatedHeaders; - - @EndpointInject("kafka:" + TOPIC_NO_RECORD_SPECIFIC_HEADERS + "?requestRequiredAcks=-1") - private Endpoint toNoRecordSpecificHeaders; - - @Produce("direct:startStrings") private ProducerTemplate stringsTemplate; - - @Produce("direct:startStrings2") private ProducerTemplate stringsTemplate2; - - @Produce("direct:startBytes") private ProducerTemplate bytesTemplate; - - @Produce("direct:startTraced") private ProducerTemplate interceptedTemplate; - - @Produce("direct:propagatedHeaders") private ProducerTemplate propagatedHeadersTemplate; - - @Produce("direct:noRecordSpecificHeaders") private ProducerTemplate noRecordSpecificHeadersTemplate; @BindToRegistry("myStrategy") - private MyHeaderFilterStrategy strategy = new MyHeaderFilterStrategy(); + private MyHeaderFilterStrategy headerFilterStrategy = new MyHeaderFilterStrategy(); @BindToRegistry("myHeaderSerializer") - private MyKafkaHeadersSerializer serializer = new MyKafkaHeadersSerializer(); + private MyKafkaHeadersSerializer headersSerializer = new MyKafkaHeadersSerializer(); @BeforeAll public static void before() { @@ -139,22 +108,73 @@ public static void after() { kafkaAdminClient.deleteTopics(topics); } + private static KafkaConsumer createStringKafkaConsumer(final String groupId) { + Properties stringsProps = new Properties(); + + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, groupId); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + + return new KafkaConsumer<>(stringsProps); + } + + private static KafkaConsumer createByteKafkaConsumer(final String groupId) { + Properties stringsProps = new Properties(); + + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, groupId); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.ByteArrayDeserializer"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.ByteArrayDeserializer"); + stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + + return new KafkaConsumer<>(stringsProps); + } + + @BeforeEach + public void setupProducerTemplates() { + stringsTemplate = contextExtension.getProducerTemplate(); + stringsTemplate2 = contextExtension.getProducerTemplate(); + bytesTemplate = contextExtension.getProducerTemplate(); + interceptedTemplate = contextExtension.getProducerTemplate(); + propagatedHeadersTemplate = contextExtension.getProducerTemplate(); + noRecordSpecificHeadersTemplate = contextExtension.getProducerTemplate(); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from("direct:startStrings").to(toStrings).to(mockEndpoint); + from(DIRECT_START_STRINGS_URI).to("kafka:" + TOPIC_STRINGS + "?requestRequiredAcks=-1").to(KAFKA_ACK_MOCK); - from("direct:startStrings2").to(toStrings2).to(mockEndpoint); + from(DIRECT_START_STRINGS_2_URI).to("kafka:" + TOPIC_STRINGS + "?requestRequiredAcks=-1").to(KAFKA_ACK_MOCK); - from("direct:startBytes").to(toBytes).to(mockEndpoint); + from(DIRECT_START_BYTES_URI).to("kafka:" + TOPIC_BYTES + "?requestRequiredAcks=-1" + + "&valueSerializer=org.apache.kafka.common.serialization.ByteArraySerializer&" + + "keySerializer=org.apache.kafka.common.serialization.ByteArraySerializer") + .to(KAFKA_ACK_MOCK); - from("direct:startTraced").to(toStringsWithInterceptor).to(mockEndpoint); + from(DIRECT_START_TRACED_URI).to("kafka:" + TOPIC_INTERCEPTED + "?requestRequiredAcks=-1" + + "&interceptorClasses=org.apache.camel.component.kafka.MockProducerInterceptor") + .to(KAFKA_ACK_MOCK); - from("direct:propagatedHeaders").to(toPropagatedHeaders).to(mockEndpoint); + from(DIRECT_PROPAGATED_HEADERS_URI).to("kafka:" + TOPIC_PROPAGATED_HEADERS + "?requestRequiredAcks=-1") + .to(KAFKA_ACK_MOCK); - from("direct:noRecordSpecificHeaders").to(toNoRecordSpecificHeaders).to(mockEndpoint); + from(DIRECT_NO_RECORD_SPECIFIC_HEADERS_URI) + .to("kafka:" + TOPIC_NO_RECORD_SPECIFIC_HEADERS + "?requestRequiredAcks=-1").to(KAFKA_ACK_MOCK); } }; } @@ -166,8 +186,10 @@ public void producedStringMessageIsReceivedByKafka() throws InterruptedException CountDownLatch messagesLatch = new CountDownLatch(messageInTopic + messageInOtherTopic); - sendMessagesInRoute(messageInTopic, stringsTemplate, "IT test message", KafkaConstants.PARTITION_KEY, "0"); - sendMessagesInRoute(messageInOtherTopic, stringsTemplate, "IT test message in other topic", + ProducerTemplate producerTemplate = contextExtension.getProducerTemplate(); + sendMessagesInRoute(DIRECT_START_STRINGS_URI, messageInTopic, stringsTemplate, "IT test message", + KafkaConstants.PARTITION_KEY, "0"); + sendMessagesInRoute(DIRECT_START_STRINGS_URI, messageInOtherTopic, stringsTemplate, "IT test message in other topic", KafkaConstants.PARTITION_KEY, "0", KafkaConstants.TOPIC, TOPIC_STRINGS_IN_HEADER); @@ -178,6 +200,7 @@ public void producedStringMessageIsReceivedByKafka() throws InterruptedException assertTrue(allMessagesReceived, "Not all messages were published to the kafka topics. Not received: " + messagesLatch.getCount()); + MockEndpoint mockEndpoint = contextExtension.getMockEndpoint(KAFKA_ACK_MOCK); List exchangeList = mockEndpoint.getExchanges(); assertEquals(15, exchangeList.size(), "Fifteen Exchanges are expected"); for (Exchange exchange : exchangeList) { @@ -197,8 +220,8 @@ public void producedString2MessageIsReceivedByKafka() throws InterruptedExceptio CountDownLatch messagesLatch = new CountDownLatch(messageInTopic + messageInOtherTopic); - sendMessagesInRoute(messageInTopic, stringsTemplate2, "IT test message", (String[]) null); - sendMessagesInRoute(messageInOtherTopic, stringsTemplate2, "IT test message in other topic", + sendMessagesInRoute(DIRECT_START_STRINGS_2_URI, messageInTopic, stringsTemplate2, "IT test message", (String[]) null); + sendMessagesInRoute(DIRECT_START_STRINGS_2_URI, messageInOtherTopic, stringsTemplate2, "IT test message in other topic", KafkaConstants.PARTITION_KEY, "0", KafkaConstants.TOPIC, TOPIC_STRINGS_IN_HEADER); @@ -209,6 +232,7 @@ public void producedString2MessageIsReceivedByKafka() throws InterruptedExceptio assertTrue(allMessagesReceived, "Not all messages were published to the kafka topics. Not received: " + messagesLatch.getCount()); + MockEndpoint mockEndpoint = contextExtension.getMockEndpoint(KAFKA_ACK_MOCK); List exchangeList = mockEndpoint.getExchanges(); assertEquals(15, exchangeList.size(), "Fifteen Exchanges are expected"); for (Exchange exchange : exchangeList) { @@ -228,8 +252,9 @@ public void producedStringMessageIsIntercepted() throws InterruptedException { CountDownLatch messagesLatch = new CountDownLatch(messageInTopic + messageInOtherTopic); - sendMessagesInRoute(messageInTopic, interceptedTemplate, "IT test message", KafkaConstants.PARTITION_KEY, "0"); - sendMessagesInRoute(messageInOtherTopic, interceptedTemplate, "IT test message in other topic", + sendMessagesInRoute(DIRECT_START_TRACED_URI, messageInTopic, interceptedTemplate, "IT test message", + KafkaConstants.PARTITION_KEY, "0"); + sendMessagesInRoute(DIRECT_START_TRACED_URI, messageInOtherTopic, interceptedTemplate, "IT test message in other topic", KafkaConstants.PARTITION_KEY, "0", KafkaConstants.TOPIC, TOPIC_STRINGS_IN_HEADER); createKafkaMessageConsumer(stringsConsumerConn, TOPIC_INTERCEPTED, TOPIC_STRINGS_IN_HEADER, messagesLatch); @@ -254,12 +279,13 @@ public void producedStringCollectionMessageIsReceivedByKafka() throws Interrupte msgs.add("Message " + x); } - sendMessagesInRoute(1, stringsTemplate, msgs, KafkaConstants.PARTITION_KEY, "0"); + sendMessagesInRoute(DIRECT_START_STRINGS_URI, 1, stringsTemplate, msgs, KafkaConstants.PARTITION_KEY, "0"); msgs = new ArrayList<>(); for (int x = 0; x < messageInOtherTopic; x++) { msgs.add("Other Message " + x); } - sendMessagesInRoute(1, stringsTemplate, msgs, KafkaConstants.PARTITION_KEY, "0", KafkaConstants.TOPIC, + sendMessagesInRoute(DIRECT_START_STRINGS_URI, 1, stringsTemplate, msgs, KafkaConstants.PARTITION_KEY, "0", + KafkaConstants.TOPIC, TOPIC_STRINGS_IN_HEADER); createKafkaMessageConsumer(stringsConsumerConn, TOPIC_STRINGS, TOPIC_STRINGS_IN_HEADER, messagesLatch); @@ -268,6 +294,7 @@ public void producedStringCollectionMessageIsReceivedByKafka() throws Interrupte assertTrue(allMessagesReceived, "Not all messages were published to the kafka topics. Not received: " + messagesLatch.getCount()); + MockEndpoint mockEndpoint = contextExtension.getMockEndpoint(KAFKA_ACK_MOCK); List exchangeList = mockEndpoint.getExchanges(); assertEquals(2, exchangeList.size(), "Two Exchanges are expected"); Exchange e1 = exchangeList.get(0); @@ -297,12 +324,14 @@ public void producedBytesMessageIsReceivedByKafka() throws InterruptedException Map inTopicHeaders = new HashMap<>(); inTopicHeaders.put(KafkaConstants.PARTITION_KEY, "0".getBytes()); - sendMessagesInRoute(messageInTopic, bytesTemplate, "IT test message".getBytes(), inTopicHeaders); + sendMessagesInRoute(DIRECT_START_BYTES_URI, messageInTopic, bytesTemplate, "IT test message".getBytes(), + inTopicHeaders); Map otherTopicHeaders = new HashMap<>(); otherTopicHeaders.put(KafkaConstants.PARTITION_KEY, "0".getBytes()); otherTopicHeaders.put(KafkaConstants.TOPIC, TOPIC_BYTES_IN_HEADER); - sendMessagesInRoute(messageInOtherTopic, bytesTemplate, "IT test message in other topic".getBytes(), otherTopicHeaders); + sendMessagesInRoute(DIRECT_START_BYTES_URI, messageInOtherTopic, bytesTemplate, + "IT test message in other topic".getBytes(), otherTopicHeaders); createKafkaBytesMessageConsumer(bytesConsumerConn, TOPIC_BYTES, TOPIC_BYTES_IN_HEADER, messagesLatch); @@ -311,6 +340,7 @@ public void producedBytesMessageIsReceivedByKafka() throws InterruptedException assertTrue(allMessagesReceived, "Not all messages were published to the kafka topics. Not received: " + messagesLatch.getCount()); + MockEndpoint mockEndpoint = contextExtension.getMockEndpoint(KAFKA_ACK_MOCK); List exchangeList = mockEndpoint.getExchanges(); assertEquals(15, exchangeList.size(), "Fifteen Exchanges are expected"); for (Exchange exchange : exchangeList) { @@ -356,7 +386,7 @@ public void propagatedHeaderIsReceivedByKafka() throws Exception { camelHeaders.put("CamelFilteredHeader", "CamelFilteredHeader value"); CountDownLatch messagesLatch = new CountDownLatch(1); - propagatedHeadersTemplate.sendBodyAndHeaders("Some test message", camelHeaders); + propagatedHeadersTemplate.sendBodyAndHeaders(DIRECT_PROPAGATED_HEADERS_URI, "Some test message", camelHeaders); List> records = pollForRecords(createStringKafkaConsumer("propagatedHeaderConsumer"), TOPIC_PROPAGATED_HEADERS, messagesLatch); @@ -397,7 +427,8 @@ public void recordSpecificHeaderIsNotReceivedByKafka() throws Exception { camelHeaders.put(propagatedStringHeaderKey, propagatedStringHeaderValue); CountDownLatch messagesLatch = new CountDownLatch(1); - noRecordSpecificHeadersTemplate.sendBodyAndHeaders("Some test message", camelHeaders); + noRecordSpecificHeadersTemplate.sendBodyAndHeaders(DIRECT_NO_RECORD_SPECIFIC_HEADERS_URI, "Some test message", + camelHeaders); List> records = pollForRecords( createStringKafkaConsumer("noRecordSpecificHeadersConsumer"), TOPIC_NO_RECORD_SPECIFIC_HEADERS, messagesLatch); @@ -416,15 +447,16 @@ public void recordSpecificHeaderIsNotReceivedByKafka() throws Exception { @Test public void headerFilterStrategyCouldBeOverridden() { KafkaEndpoint kafkaEndpoint - = context.getEndpoint("kafka:TOPIC_PROPAGATED_HEADERS?headerFilterStrategy=#myStrategy", KafkaEndpoint.class); - assertIsInstanceOf(MyHeaderFilterStrategy.class, kafkaEndpoint.getConfiguration().getHeaderFilterStrategy()); + = contextExtension.getContext().getEndpoint("kafka:TOPIC_PROPAGATED_HEADERS?headerFilterStrategy=#myStrategy", + KafkaEndpoint.class); + assertInstanceOf(MyHeaderFilterStrategy.class, kafkaEndpoint.getConfiguration().getHeaderFilterStrategy()); } @Test public void headerSerializerCouldBeOverridden() { - KafkaEndpoint kafkaEndpoint = context + KafkaEndpoint kafkaEndpoint = contextExtension.getContext() .getEndpoint("kafka:TOPIC_PROPAGATED_HEADERS?headerSerializer=#myHeaderSerializer", KafkaEndpoint.class); - assertIsInstanceOf(MyKafkaHeadersSerializer.class, kafkaEndpoint.getConfiguration().getHeaderSerializer()); + assertInstanceOf(MyKafkaHeadersSerializer.class, kafkaEndpoint.getConfiguration().getHeaderSerializer()); } private byte[] getHeaderValue(String headerKey, Headers headers) { @@ -434,40 +466,6 @@ private byte[] getHeaderValue(String headerKey, Headers headers) { return foundHeader.value(); } - private static KafkaConsumer createStringKafkaConsumer(final String groupId) { - Properties stringsProps = new Properties(); - - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, groupId); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - - return new KafkaConsumer<>(stringsProps); - } - - private static KafkaConsumer createByteKafkaConsumer(final String groupId) { - Properties stringsProps = new Properties(); - - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG, groupId); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.ByteArrayDeserializer"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.ByteArrayDeserializer"); - stringsProps.put(org.apache.kafka.clients.consumer.ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - - return new KafkaConsumer<>(stringsProps); - } - private List> pollForRecords( KafkaConsumer consumerConn, String topic, CountDownLatch messagesLatch) { @@ -522,22 +520,6 @@ private void createKafkaBytesMessageConsumer( } - private void sendMessagesInRoute(int messages, ProducerTemplate template, Object bodyOther, String... headersWithValue) { - Map headerMap = new HashMap<>(); - if (headersWithValue != null) { - for (int i = 0; i < headersWithValue.length; i = i + 2) { - headerMap.put(headersWithValue[i], headersWithValue[i + 1]); - } - } - sendMessagesInRoute(messages, template, bodyOther, headerMap); - } - - private void sendMessagesInRoute(int messages, ProducerTemplate template, Object bodyOther, Map headerMap) { - for (int k = 0; k < messages; k++) { - template.sendBodyAndHeaders(bodyOther, headerMap); - } - } - private static class MyHeaderFilterStrategy extends DefaultHeaderFilterStrategy { } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaToDIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaToDIT.java index 8234d5c312dcf..330a50d1f9006 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaToDIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaToDIT.java @@ -25,11 +25,12 @@ public class KafkaToDIT extends BaseEmbeddedKafkaTestSupport { @Test public void testToD() { - template.sendBodyAndHeader("direct:start", "Hello bar", "where", "bar"); - template.sendBodyAndHeader("direct:start", "Hello beer", "where", "beer"); + contextExtension.getProducerTemplate().sendBodyAndHeader("direct:start", "Hello bar", "where", "bar"); + contextExtension.getProducerTemplate().sendBodyAndHeader("direct:start", "Hello beer", "where", "beer"); // there should only be one kafka endpoint - long count = context.getEndpoints().stream().filter(e -> e.getEndpointUri().startsWith("kafka:")).count(); + long count = contextExtension.getContext().getEndpoints().stream().filter(e -> e.getEndpointUri().startsWith("kafka:")) + .count(); assertEquals(1, count, "There should only be 1 kafka endpoint"); } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaTransactionIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaTransactionIT.java index eefabf142c886..68efdf8088ee8 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaTransactionIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/KafkaTransactionIT.java @@ -19,21 +19,18 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.Exchange; import org.apache.camel.Processor; -import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; +import org.apache.camel.component.kafka.integration.common.TestProducerUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; @@ -48,35 +45,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class KafkaTransactionIT extends BaseEmbeddedKafkaTestSupport { + public static final String SEQUENTIAL_TRANSACTION_URI = "direct:startTransaction"; + public static final String CONCURRENT_TRANSACTION_URI = "seda:startTransaction"; + private static final String TOPIC_TRANSACTION = "transaction"; private static final String TOPIC_CONCURRENCY_TRANSACTION = "concurrency_transaction"; - private static KafkaConsumer stringsConsumerConn; private static final int THREAD_NUM = 5; - - @EndpointInject("kafka:" + TOPIC_TRANSACTION + "?requestRequiredAcks=-1" - + "&additional-properties[transactional.id]=1234" - + "&additional-properties[enable.idempotence]=true" - + "&additional-properties[retries]=5") - private Endpoint toTransaction; - - @EndpointInject("kafka:" + TOPIC_CONCURRENCY_TRANSACTION + "?requestRequiredAcks=-1&synchronous=true" - + "&additional-properties[transactional.id]=5678" - + "&additional-properties[enable.idempotence]=true" - + "&additional-properties[retries]=5") - private Endpoint toConcurrencyTransaction; - - @EndpointInject("mock:kafkaAck") - private MockEndpoint mockEndpoint; - - @Produce("direct:startTransaction") - private ProducerTemplate testTransaction; - - @Produce("seda:startTransaction") - private ProducerTemplate testConcurrencyTransaction; - - public KafkaTransactionIT() { - - } + private static KafkaConsumer stringsConsumerConn; @BeforeAll public static void before() { @@ -97,7 +72,10 @@ protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() throws Exception { - from("direct:startTransaction").to(toTransaction) + from(SEQUENTIAL_TRANSACTION_URI).to("kafka:" + TOPIC_TRANSACTION + "?requestRequiredAcks=-1" + + "&additional-properties[transactional.id]=1234" + + "&additional-properties[enable.idempotence]=true" + + "&additional-properties[retries]=5") .process(new Processor() { @Override public void process(Exchange exchange) throws Exception { @@ -106,9 +84,13 @@ public void process(Exchange exchange) throws Exception { throw new RuntimeException("fail process message " + body); } } - }).to(mockEndpoint); + }).to(KafkaTestUtil.MOCK_RESULT); - from("seda:startTransaction").to(toConcurrencyTransaction); + from(CONCURRENT_TRANSACTION_URI) + .to("kafka:" + TOPIC_CONCURRENCY_TRANSACTION + "?requestRequiredAcks=-1&synchronous=true" + + "&additional-properties[transactional.id]=5678" + + "&additional-properties[enable.idempotence]=true" + + "&additional-properties[retries]=5"); } }; } @@ -124,7 +106,10 @@ public void concurrencyProducedTransactionMessage() throws InterruptedException threads[i] = new Thread(new Runnable() { @Override public void run() { - sendMessagesInRoute(messageInTopic, testConcurrencyTransaction, "IT test concurrency transaction message", + ProducerTemplate testConcurrencyTransaction = contextExtension.getProducerTemplate(); + + TestProducerUtil.sendMessagesInRoute(CONCURRENT_TRANSACTION_URI, messageInTopic, testConcurrencyTransaction, + "IT test concurrency transaction message", KafkaConstants.PARTITION_KEY, "0"); } @@ -150,11 +135,15 @@ public void producedTransactionMassageIsReceivedByKafka() throws InterruptedExce CountDownLatch messagesLatch = new CountDownLatch(messageInTopic); - sendMessagesInRoute(messageInTopic, testTransaction, "IT test transaction message", KafkaConstants.PARTITION_KEY, "0"); + ProducerTemplate testTransaction = contextExtension.getProducerTemplate(); + TestProducerUtil.sendMessagesInRoute(SEQUENTIAL_TRANSACTION_URI, messageInTopic, testTransaction, + "IT test transaction message", + KafkaConstants.PARTITION_KEY, "0"); assertThrows(RuntimeException.class, new Executable() { @Override - public void execute() throws Throwable { - sendMessagesInRoute(messageInTopic, testTransaction, "IT test transaction fail message", + public void execute() { + TestProducerUtil.sendMessagesInRoute(SEQUENTIAL_TRANSACTION_URI, messageInTopic, testTransaction, + "IT test transaction fail message", KafkaConstants.PARTITION_KEY, "0"); } @@ -167,6 +156,8 @@ public void execute() throws Throwable { assertTrue(allMessagesReceived, "Not all messages were published to the kafka topics. Not received: " + messagesLatch.getCount()); + MockEndpoint mockEndpoint = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + List exchangeList = mockEndpoint.getExchanges(); assertEquals(10, exchangeList.size(), "Ten Exchanges are expected"); for (Exchange exchange : exchangeList) { @@ -214,19 +205,4 @@ private void createKafkaMessageConsumer( } } - private void sendMessagesInRoute(int messages, ProducerTemplate template, Object bodyOther, String... headersWithValue) { - Map headerMap = new HashMap<>(); - if (headersWithValue != null) { - for (int i = 0; i < headersWithValue.length; i = i + 2) { - headerMap.put(headersWithValue[i], headersWithValue[i + 1]); - } - } - sendMessagesInRoute(messages, template, bodyOther, headerMap); - } - - private void sendMessagesInRoute(int messages, ProducerTemplate template, Object bodyOther, Map headerMap) { - for (int k = 0; k < messages; k++) { - template.sendBodyAndHeaders(bodyOther, headerMap); - } - } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseManualCommitTestSupport.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/BaseManualCommitTestSupport.java similarity index 90% rename from components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseManualCommitTestSupport.java rename to components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/BaseManualCommitTestSupport.java index ffbadfc60287b..00870088828a6 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseManualCommitTestSupport.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/BaseManualCommitTestSupport.java @@ -15,13 +15,14 @@ * limitations under the License. */ -package org.apache.camel.component.kafka.integration; +package org.apache.camel.component.kafka.integration.commit; import java.util.Collections; import java.util.Properties; import org.apache.camel.EndpointInject; import org.apache.camel.component.kafka.KafkaConstants; +import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.spi.StateRepository; import org.apache.kafka.clients.producer.ProducerRecord; @@ -31,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class BaseManualCommitTestSupport extends BaseEmbeddedKafkaTestSupport { +abstract class BaseManualCommitTestSupport extends BaseEmbeddedKafkaTestSupport { @EndpointInject("mock:result") protected MockEndpoint to; @@ -65,7 +66,7 @@ public void kafkaManualCommitTest(String topic) throws Exception { to.reset(); // Second step: We shut down our route, we expect nothing will be recovered by our route - context.getRouteController().stopRoute("foo"); + contextExtension.getContext().getRouteController().stopRoute("foo"); to.expectedMessageCount(0); // Third step: While our route is stopped, we send 3 records more to Kafka test topic @@ -77,7 +78,7 @@ public void kafkaManualCommitTest(String topic) throws Exception { // Fourth step: We start again our route, since we have been committing the offsets from the first step, // we will expect to consume from the latest committed offset e.g from offset 5 - context.getRouteController().startRoute("foo"); + contextExtension.getContext().getRouteController().startRoute("foo"); setupPostExecutionExpectations(); to.assertIsSatisfied(3000); @@ -99,7 +100,7 @@ public void kafkaManualCommitTestWithStateRepository(String topic, StateReposito assertEquals("5", state, "5 messages were sent in the first step, therefore the offset should be 5"); // Second step: We shut down our route, we expect nothing will be recovered by our route - context.getRouteController().stopRoute("foo"); + contextExtension.getContext().getRouteController().stopRoute("foo"); to.expectedMessageCount(0); // Third step: While our route is stopped, we send 3 records more to Kafka test topic @@ -111,7 +112,7 @@ public void kafkaManualCommitTestWithStateRepository(String topic, StateReposito // Fourth step: We start again our route, since we have been committing the offsets from the first step, // we will expect to consume from the latest committed offset e.g from offset 5 - context.getRouteController().startRoute("foo"); + contextExtension.getContext().getRouteController().startRoute("foo"); setupPostExecutionExpectations(); to.assertIsSatisfied(3000); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncCommitIT.java index 5387ba7d3e26a..be65f1a3ce98c 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncCommitIT.java @@ -16,12 +16,10 @@ */ package org.apache.camel.component.kafka.integration.commit; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; -import org.apache.camel.component.kafka.integration.BaseManualCommitTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.RepeatedTest; @@ -31,11 +29,6 @@ public class KafkaConsumerAsyncCommitIT extends BaseManualCommitTestSupport { public static final String TOPIC = "testManualAsyncCommitTest"; - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerAsyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" - + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory") - private Endpoint from; - @AfterEach public void after() { cleanupKafka(TOPIC); @@ -47,12 +40,19 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to(to).process(e -> { - KafkaManualCommit manual = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); - assertNotNull(manual); - manual.commit(); - }); - from(from).routeId("bar").autoStartup(false).to(toBar); + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerAsyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory") + .routeId("foo").to(KafkaTestUtil.MOCK_RESULT).process(e -> { + KafkaManualCommit manual + = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); + assertNotNull(manual); + manual.commit(); + }); + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerAsyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory") + .routeId("bar").autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncWithOffsetRepoCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncWithOffsetRepoCommitIT.java index dcdc9e990d588..d711fa916f8be 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncWithOffsetRepoCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerAsyncWithOffsetRepoCommitIT.java @@ -17,12 +17,10 @@ package org.apache.camel.component.kafka.integration.commit; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; -import org.apache.camel.component.kafka.integration.BaseManualCommitTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.impl.engine.MemoryStateRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -35,18 +33,7 @@ public class KafkaConsumerAsyncWithOffsetRepoCommitIT extends BaseManualCommitTe public static final String TOPIC = "testAsyncCommitWithOffsetRepoTest"; @BindToRegistry("stateRepository") - private final MemoryStateRepository stateRepository = new MemoryStateRepository(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerAsyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false&offsetRepository=#bean:stateRepository" - + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory") - private Endpoint from; - - public static MemoryStateRepository offsetRepo() { - MemoryStateRepository stateRepository = new MemoryStateRepository(); - stateRepository.setState(TOPIC + "/0", ""); - return stateRepository; - } + private static MemoryStateRepository stateRepository = new MemoryStateRepository(); @AfterEach public void after() { @@ -59,15 +46,20 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from) - .routeId("foo").to(to).process(e -> { + final String uri = "kafka:" + TOPIC + + "?groupId=KafkaConsumerAsyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false&offsetRepository=#bean:stateRepository" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualAsyncCommitFactory"; + + from(uri) + .routeId("foo").to(KafkaTestUtil.MOCK_RESULT).process(e -> { KafkaManualCommit manual = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); assertNotNull(manual); manual.commit(); }); - from(from).routeId("bar").autoStartup(false).to(toBar); + from(uri) + .routeId("bar").autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerNoopCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerNoopCommitIT.java index 344e5b3f64816..e563b21e45735 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerNoopCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerNoopCommitIT.java @@ -16,12 +16,11 @@ */ package org.apache.camel.component.kafka.integration.commit; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; -import org.apache.camel.component.kafka.integration.BaseManualCommitTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.kafka.clients.producer.ProducerRecord; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -32,11 +31,6 @@ public class KafkaConsumerNoopCommitIT extends BaseManualCommitTestSupport { public static final String TOPIC = "testManualNoopCommitTest"; - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerNoopCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" - + "&allowManualCommit=true&autoOffsetReset=earliest&metadataMaxAgeMs=1000") - private Endpoint from; - @AfterEach public void after() { cleanupKafka(TOPIC); @@ -48,12 +42,19 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to(to).process(e -> { - KafkaManualCommit manual = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); - assertNotNull(manual); - manual.commit(); - }); - from(from).routeId("bar").autoStartup(false).to(toBar); + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerNoopCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&metadataMaxAgeMs=1000").routeId("foo") + .to(KafkaTestUtil.MOCK_RESULT).process(e -> { + KafkaManualCommit manual + = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); + assertNotNull(manual); + manual.commit(); + }); + from("kafka:" + TOPIC + + "?groupId=KafkaConsumerNoopCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&metadataMaxAgeMs=1000").routeId("bar") + .autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } @@ -71,6 +72,7 @@ public void kafkaAutoCommitDisabledDuringRebalance() throws Exception { to.reset(); + CamelContext context = contextExtension.getContext(); context.getRouteController().stopRoute("foo"); to.expectedMessageCount(0); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncCommitIT.java index b6eb9f7791dbe..b145bd20e7775 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncCommitIT.java @@ -16,12 +16,10 @@ */ package org.apache.camel.component.kafka.integration.commit; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; -import org.apache.camel.component.kafka.integration.BaseManualCommitTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.RepeatedTest; @@ -31,11 +29,6 @@ public class KafkaConsumerSyncCommitIT extends BaseManualCommitTestSupport { public static final String TOPIC = "testManualCommitSyncTest"; - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerSyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" - + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualCommitFactory") - private Endpoint from; - @AfterEach public void after() { cleanupKafka(TOPIC); @@ -43,16 +36,20 @@ public void after() { @Override protected RouteBuilder createRouteBuilder() { + String from = "kafka:" + TOPIC + + "?groupId=KafkaConsumerSyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualCommitFactory"; + return new RouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to(to).process(e -> { + from(from).routeId("foo").to(KafkaTestUtil.MOCK_RESULT).process(e -> { KafkaManualCommit manual = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); assertNotNull(manual); manual.commit(); }); - from(from).routeId("bar").autoStartup(false).to(toBar); + from(from).routeId("bar").autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncWithOffsetRepoCommitIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncWithOffsetRepoCommitIT.java index 159d1c9499eda..e6f02495ed5c9 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncWithOffsetRepoCommitIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/commit/KafkaConsumerSyncWithOffsetRepoCommitIT.java @@ -17,12 +17,10 @@ package org.apache.camel.component.kafka.integration.commit; import org.apache.camel.BindToRegistry; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.consumer.KafkaManualCommit; -import org.apache.camel.component.kafka.integration.BaseManualCommitTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.impl.engine.MemoryStateRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -30,17 +28,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; -public class KafkaConsumerSyncWithOffsetRepoCommitIT extends BaseManualCommitTestSupport { +class KafkaConsumerSyncWithOffsetRepoCommitIT extends BaseManualCommitTestSupport { public static final String TOPIC = "testManualCommitSyncWithOffsetRepoTest"; @BindToRegistry("stateRepository") - private final MemoryStateRepository stateRepository = new MemoryStateRepository(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerSyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false&offsetRepository=#bean:stateRepository" - + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualCommitFactory") - private Endpoint from; + private static MemoryStateRepository stateRepository = new MemoryStateRepository(); @AfterEach public void after() { @@ -53,12 +46,18 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from).routeId("foo").to(to).process(e -> { - KafkaManualCommit manual = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); - assertNotNull(manual); - manual.commit(); - }); - from(from).routeId("bar").autoStartup(false).to(toBar); + final String from = "kafka:" + TOPIC + + "?groupId=KafkaConsumerSyncCommitIT&pollTimeoutMs=1000&autoCommitEnable=false&offsetRepository=#bean:stateRepository" + + "&allowManualCommit=true&autoOffsetReset=earliest&kafkaManualCommitFactory=#class:org.apache.camel.component.kafka.consumer.DefaultKafkaManualCommitFactory"; + from(from).routeId("foo") + .to(KafkaTestUtil.MOCK_RESULT).process(e -> { + KafkaManualCommit manual + = e.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class); + assertNotNull(manual); + manual.commit(); + }); + from(from) + .routeId("bar").autoStartup(false).to(KafkaTestUtil.MOCK_RESULT_BAR); } }; } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaAuthTestSupport.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaAdminUtil.java similarity index 61% rename from components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaAuthTestSupport.java rename to components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaAdminUtil.java index adf2b12068e12..62083957a4186 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/BaseEmbeddedKafkaAuthTestSupport.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaAdminUtil.java @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.kafka.integration; + +package org.apache.camel.component.kafka.integration.common; import java.util.Collections; import java.util.Map; @@ -23,80 +24,53 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.camel.CamelContext; import org.apache.camel.test.infra.kafka.services.ContainerLocalAuthKafkaService; import org.apache.camel.test.infra.kafka.services.KafkaService; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.AdminClientConfig; import org.apache.kafka.clients.admin.ConsumerGroupDescription; +import org.apache.kafka.clients.admin.KafkaAdminClient; import org.apache.kafka.common.config.SaslConfigs; import org.junit.Assert; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Tags; -import org.junit.jupiter.api.extension.RegisterExtension; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -@Tags({ @Tag("non-abstract") }) -public abstract class BaseEmbeddedKafkaAuthTestSupport extends AbstractKafkaTestSupport { - @RegisterExtension - public static ContainerLocalAuthKafkaService service = new ContainerLocalAuthKafkaService("/kafka-jaas.config"); +public final class KafkaAdminUtil { - protected static AdminClient kafkaAdminClient; + private KafkaAdminUtil() { - @BeforeAll - public static void beforeClass() { - AbstractKafkaTestSupport.setServiceProperties(service); } - public static AdminClient createAuthAdminClient(KafkaService service) { + public static AdminClient createAdminClient(KafkaService service) { final Properties properties = new Properties(); properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, service.getBootstrapServers()); - properties.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT"); - properties.put(SaslConfigs.SASL_MECHANISM, "PLAIN"); - properties.put(SaslConfigs.SASL_JAAS_CONFIG, - ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("admin", "admin-secret")); - - return AdminClient.create(properties); - } - - @BeforeEach - public void setKafkaAdminClient() { - if (kafkaAdminClient == null) { - kafkaAdminClient = createAdminClient(); - } - } - - protected Properties getDefaultProperties() { - return getDefaultProperties(service); - } - - @Override - protected CamelContext createCamelContext() throws Exception { - return createCamelContextFromService(service); - } - - protected static String getBootstrapServers() { - return service.getBootstrapServers(); - } - private static AdminClient createAdminClient() { - return createAuthAdminClient(service); + return KafkaAdminClient.create(properties); } - protected static Map getConsumerGroupInfo(String groupId) + public static Map getConsumerGroupInfo(String groupId, AdminClient kafkaAdminClient) throws InterruptedException, ExecutionException, TimeoutException { return kafkaAdminClient.describeConsumerGroups(Collections.singletonList(groupId)).all().get(30, TimeUnit.SECONDS); } - protected static void assertGroupIsConnected(String groupId) { - final Map allGroups = assertDoesNotThrow(() -> getConsumerGroupInfo(groupId)); + public static void assertGroupIsConnected(String groupId, AdminClient kafkaAdminClient) { + final Map allGroups + = assertDoesNotThrow(() -> getConsumerGroupInfo(groupId, kafkaAdminClient)); Assert.assertTrue("There should be at least one group named" + groupId, allGroups.size() >= 1); final ConsumerGroupDescription groupInfo = allGroups.get("KafkaConsumerAuthIT"); Assert.assertNotNull("There should be at least one group named KafkaConsumerAuthIT", groupInfo); } + + public static AdminClient createAuthAdminClient(KafkaService service) { + final Properties properties = new Properties(); + properties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, service.getBootstrapServers()); + properties.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT"); + properties.put(SaslConfigs.SASL_MECHANISM, "PLAIN"); + properties.put(SaslConfigs.SASL_JAAS_CONFIG, + ContainerLocalAuthKafkaService.generateSimpleSaslJaasConfig("admin", "admin-secret")); + + return AdminClient.create(properties); + } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/AbstractKafkaTestSupport.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaTestUtil.java similarity index 72% rename from components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/AbstractKafkaTestSupport.java rename to components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaTestUtil.java index 6bb36ecf4c1c7..3e85ab5f5d170 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/AbstractKafkaTestSupport.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/KafkaTestUtil.java @@ -15,15 +15,12 @@ * limitations under the License. */ -package org.apache.camel.component.kafka.integration; +package org.apache.camel.component.kafka.integration.common; import java.util.Properties; -import org.apache.camel.CamelContext; -import org.apache.camel.component.kafka.KafkaComponent; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.test.infra.kafka.services.KafkaService; -import org.apache.camel.test.junit5.CamelTestSupport; import org.apache.kafka.clients.admin.AdminClient; import org.apache.kafka.clients.admin.AdminClientConfig; import org.apache.kafka.clients.admin.KafkaAdminClient; @@ -31,10 +28,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractKafkaTestSupport extends CamelTestSupport { - private static final Logger LOG = LoggerFactory.getLogger(AbstractKafkaTestSupport.class); +public final class KafkaTestUtil { + public static final String MOCK_RESULT = "mock:result"; + public static final String MOCK_RESULT_BAR = "mock:resultBar"; + public static final String MOCK_DLQ = "mock:dlq"; - protected static void setServiceProperties(KafkaService service) { + private static final Logger LOG = LoggerFactory.getLogger(KafkaTestUtil.class); + + private KafkaTestUtil() { + + } + + public static void setServiceProperties(KafkaService service) { LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); System.setProperty("bootstrapServers", service.getBootstrapServers()); } @@ -61,19 +66,4 @@ public static Properties getDefaultProperties(String bootstrapService) { public static Properties getDefaultProperties(KafkaService service) { return getDefaultProperties(service.getBootstrapServers()); } - - protected CamelContext createCamelContextFromService(KafkaService service) throws Exception { - CamelContext context = super.createCamelContext(); - context.getPropertiesComponent().setLocation("ref:prop"); - - KafkaComponent kafka = new KafkaComponent(context); - kafka.init(); - kafka.getConfiguration().setBrokers(service.getBootstrapServers()); - context.addComponent("kafka", kafka); - - return context; - } - - protected abstract Properties getDefaultProperties(); - } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/TestProducerUtil.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/TestProducerUtil.java new file mode 100644 index 0000000000000..1f72811f86f43 --- /dev/null +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/common/TestProducerUtil.java @@ -0,0 +1,48 @@ +/* + * 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.kafka.integration.common; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.ProducerTemplate; + +public final class TestProducerUtil { + + private TestProducerUtil() { + + } + + public static void sendMessagesInRoute( + String uri, int messages, ProducerTemplate template, Object bodyOther, String... headersWithValue) { + Map headerMap = new HashMap<>(); + if (headersWithValue != null) { + for (int i = 0; i < headersWithValue.length; i = i + 2) { + headerMap.put(headersWithValue[i], headersWithValue[i + 1]); + } + } + sendMessagesInRoute(uri, messages, template, bodyOther, headerMap); + } + + public static void sendMessagesInRoute( + String uri, int messages, ProducerTemplate template, Object bodyOther, Map headerMap) { + for (int k = 0; k < messages; k++) { + template.sendBodyAndHeaders(uri, bodyOther, headerMap); + } + } +} diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortHealthCheckIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortHealthCheckIT.java index c0e680cb0e741..d9d03cb79ebc3 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortHealthCheckIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortHealthCheckIT.java @@ -17,124 +17,52 @@ package org.apache.camel.component.kafka.integration.health; import java.util.Collection; -import java.util.Collections; -import java.util.Properties; import java.util.concurrent.TimeUnit; -import org.apache.camel.BindToRegistry; import org.apache.camel.CamelContext; -import org.apache.camel.Endpoint; import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaComponent; -import org.apache.camel.component.kafka.MockConsumerInterceptor; -import org.apache.camel.component.kafka.integration.AbstractKafkaTestSupport; -import org.apache.camel.component.kafka.serde.DefaultKafkaHeaderDeserializer; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.health.HealthCheck; import org.apache.camel.health.HealthCheckHelper; -import org.apache.camel.health.HealthCheckRegistry; -import org.apache.camel.impl.health.DefaultHealthCheckRegistry; -import org.apache.camel.test.infra.kafka.services.KafkaService; -import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.apache.kafka.clients.admin.AdminClient; +import org.apache.camel.test.infra.core.ContextFixture; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - +@TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisabledIfSystemProperty(named = "kafka.instance.type", matches = "local-strimzi-container", disabledReason = "Test infra Kafka runs the Strimzi containers in a way that conflicts with multiple concurrent images") -public class KafkaConsumerBadPortHealthCheckIT extends CamelTestSupport { +public class KafkaConsumerBadPortHealthCheckIT extends KafkaHealthCheckTestSupport { public static final String TOPIC = "test-health"; - @RegisterExtension - public static KafkaService service = KafkaServiceFactory.createService(); - - protected static AdminClient kafkaAdminClient; - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerBadPortHealthCheckIT.class); - @BindToRegistry("myHeaderDeserializer") - private MyKafkaHeaderDeserializer deserializer = new MyKafkaHeaderDeserializer(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerBadPortHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; @EndpointInject("mock:result") private MockEndpoint to; - private org.apache.kafka.clients.producer.KafkaProducer producer; - - @BeforeEach - public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); - producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); - MockConsumerInterceptor.recordsCaptured.clear(); - } - - @BeforeAll - public static void beforeClass() { - LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); - System.setProperty("bootstrapServers", service.getBootstrapServers()); - System.setProperty("brokers", service.getBootstrapServers()); - } - - @BeforeEach - public void setKafkaAdminClient() { - if (kafkaAdminClient == null) { - kafkaAdminClient = AbstractKafkaTestSupport.createAdminClient(service); - } - } - - @AfterEach - public void after() { - if (producer != null) { - producer.close(); - } - // clean all test topics - kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); - } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext context = super.createCamelContext(); + @ContextFixture + public void configureContext(CamelContext context) { context.getPropertiesComponent().setLocation("ref:prop"); KafkaComponent kafka = new KafkaComponent(context); kafka.init(); kafka.getConfiguration().setBrokers(service.getBootstrapServers() + 123); context.addComponent("kafka", kafka); - - // install health check manually (yes a bit cumbersome) - HealthCheckRegistry registry = new DefaultHealthCheckRegistry(); - registry.setCamelContext(context); - Object hc = registry.resolveById("context"); - registry.register(hc); - hc = registry.resolveById("routes"); - registry.register(hc); - hc = registry.resolveById("consumers"); - registry.register(hc); - context.setExtension(HealthCheckRegistry.class, registry); - - return context; } @Override @@ -143,6 +71,10 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { + String from = "kafka:" + TOPIC + + "?groupId=KafkaConsumerBadPortHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" + + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor"; from(from) .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) .routeId("test-health-it").to(to); @@ -152,11 +84,20 @@ public void configure() { @Order(1) @Test - public void kafkaConsumerHealthCheck() throws InterruptedException { - // health-check liveness should be UP + @DisplayName("Tests that liveness reports UP when it's actually up") + public void testReportUpWhenIsUp() { + CamelContext context = contextExtension.getContext(); + Collection res = HealthCheckHelper.invokeLiveness(context); boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); Assertions.assertTrue(up, "liveness check"); + } + + @Order(2) + @Test + @DisplayName("Tests that readiness reports down when it's actually down") + public void testReportCorrectlyWhenDown() { + CamelContext context = contextExtension.getContext(); // health-check readiness should be down await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { @@ -167,7 +108,11 @@ public void kafkaConsumerHealthCheck() throws InterruptedException { }); Assertions.assertTrue(up2, "readiness check"); }); + } + @Order(3) + @Test + public void kafkaConsumerHealthCheck() throws InterruptedException { String propagatedHeaderKey = "PropagatedCustomHeader"; byte[] propagatedHeaderValue = "propagated header value".getBytes(); to.expectedMessageCount(0); @@ -184,7 +129,4 @@ public void kafkaConsumerHealthCheck() throws InterruptedException { to.assertIsSatisfied(3000); } - - private static class MyKafkaHeaderDeserializer extends DefaultKafkaHeaderDeserializer { - } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortSupervisingHealthCheckIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortSupervisingHealthCheckIT.java index 052f81674e4a7..bc804d66b00db 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortSupervisingHealthCheckIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerBadPortSupervisingHealthCheckIT.java @@ -17,107 +17,45 @@ package org.apache.camel.component.kafka.integration.health; import java.util.Collection; -import java.util.Collections; -import java.util.Properties; import java.util.concurrent.TimeUnit; -import org.apache.camel.BindToRegistry; import org.apache.camel.CamelContext; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaComponent; -import org.apache.camel.component.kafka.MockConsumerInterceptor; -import org.apache.camel.component.kafka.integration.AbstractKafkaTestSupport; -import org.apache.camel.component.kafka.serde.DefaultKafkaHeaderDeserializer; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.health.HealthCheck; import org.apache.camel.health.HealthCheckHelper; -import org.apache.camel.health.HealthCheckRegistry; import org.apache.camel.impl.engine.DefaultSupervisingRouteController; -import org.apache.camel.impl.health.DefaultHealthCheckRegistry; import org.apache.camel.spi.SupervisingRouteController; -import org.apache.camel.test.infra.kafka.services.KafkaService; -import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.apache.kafka.clients.admin.AdminClient; +import org.apache.camel.test.infra.core.ContextFixture; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.testcontainers.shaded.org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.await; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - +@TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisabledIfSystemProperty(named = "kafka.instance.type", matches = "local-strimzi-container", disabledReason = "Test infra Kafka runs the Strimzi containers in a way that conflicts with multiple concurrent images") -public class KafkaConsumerBadPortSupervisingHealthCheckIT extends CamelTestSupport { +public class KafkaConsumerBadPortSupervisingHealthCheckIT extends KafkaHealthCheckTestSupport { public static final String TOPIC = "test-health"; - @RegisterExtension - public static KafkaService service = KafkaServiceFactory.createService(); - - protected static AdminClient kafkaAdminClient; - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerBadPortSupervisingHealthCheckIT.class); - @BindToRegistry("myHeaderDeserializer") - private MyKafkaHeaderDeserializer deserializer = new MyKafkaHeaderDeserializer(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerBadPortSupervisingHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - @EndpointInject("mock:result") - private MockEndpoint to; - - private org.apache.kafka.clients.producer.KafkaProducer producer; - - @BeforeEach - public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); - producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); - MockConsumerInterceptor.recordsCaptured.clear(); - } - - @BeforeAll - public static void beforeClass() { - LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); - System.setProperty("bootstrapServers", service.getBootstrapServers()); - System.setProperty("brokers", service.getBootstrapServers()); - } - - @BeforeEach - public void setKafkaAdminClient() { - if (kafkaAdminClient == null) { - kafkaAdminClient = AbstractKafkaTestSupport.createAdminClient(service); - } - } - - @AfterEach - public void after() { - if (producer != null) { - producer.close(); - } - // clean all test topics - kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); - } - + @ContextFixture @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext context = super.createCamelContext(); + public void configureContext(CamelContext context) { context.getPropertiesComponent().setLocation("ref:prop"); context.setRouteController(new DefaultSupervisingRouteController()); @@ -130,19 +68,6 @@ protected CamelContext createCamelContext() throws Exception { kafka.init(); kafka.getConfiguration().setBrokers(service.getBootstrapServers() + 123); context.addComponent("kafka", kafka); - - // install health check manually (yes a bit cumbersome) - HealthCheckRegistry registry = new DefaultHealthCheckRegistry(); - registry.setCamelContext(context); - Object hc = registry.resolveById("context"); - registry.register(hc); - hc = registry.resolveById("routes"); - registry.register(hc); - hc = registry.resolveById("consumers"); - registry.register(hc); - context.setExtension(HealthCheckRegistry.class, registry); - - return context; } @Override @@ -151,33 +76,57 @@ protected RouteBuilder createRouteBuilder() { @Override public void configure() { - from(from) + String uri = "kafka:" + TOPIC + + "?groupId=KafkaConsumerBadPortSupervisingHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" + + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor"; + + from(uri) .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) - .routeId("test-health-it").to(to); + .routeId("test-health-it").to(KafkaTestUtil.MOCK_RESULT); } }; } @Order(1) @Test - public void kafkaConsumerHealthCheck() throws InterruptedException { + @DisplayName("Tests that liveness reports UP when it's actually up") + public void testReportUpWhenIsUp() { // health-check liveness should be UP + CamelContext context = contextExtension.getContext(); + Collection res = HealthCheckHelper.invokeLiveness(context); boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); Assertions.assertTrue(up, "liveness check"); + } + + @Order(2) + @Test + @DisplayName("Tests that readiness reports down when it's actually down") + public void testReportCorrectlyWhenDown() { + CamelContext context = contextExtension.getContext(); // health-check readiness should be down - await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { - Collection res2 = HealthCheckHelper.invokeReadiness(context); - boolean up2 = res2.stream().allMatch(r -> { - return r.getState().equals(HealthCheck.State.DOWN) && - r.getMessage().stream().allMatch(msg -> msg.contains("port")); - }); - Assertions.assertTrue(up2, "readiness check"); + await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> readinessCheck(context)); + } + + private static void readinessCheck(CamelContext context) { + Collection res2 = HealthCheckHelper.invokeReadiness(context); + boolean up2 = res2.stream().allMatch(r -> { + return r.getState().equals(HealthCheck.State.DOWN) && + r.getMessage().stream().allMatch(msg -> msg.contains("port")); }); + Assertions.assertTrue(up2, "readiness check"); + } + @Order(3) + @Test + @DisplayName("I/O test to ensure everything is working as expected") + public void kafkaConsumerHealthCheck() throws InterruptedException { String propagatedHeaderKey = "PropagatedCustomHeader"; byte[] propagatedHeaderValue = "propagated header value".getBytes(); + + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); to.expectedMessageCount(0); to.expectedMinimumMessageCount(0); to.expectedNoHeaderReceived(); @@ -192,7 +141,4 @@ public void kafkaConsumerHealthCheck() throws InterruptedException { to.assertIsSatisfied(3000); } - - private static class MyKafkaHeaderDeserializer extends DefaultKafkaHeaderDeserializer { - } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerHealthCheckIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerHealthCheckIT.java new file mode 100644 index 0000000000000..636b1e33d96e3 --- /dev/null +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerHealthCheckIT.java @@ -0,0 +1,188 @@ +/* + * 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.kafka.integration.health; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.stream.StreamSupport; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.kafka.KafkaConstants; +import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; +import org.apache.camel.health.HealthCheck; +import org.apache.camel.health.HealthCheckHelper; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.kafka.services.KafkaService; +import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +@Timeout(30) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class KafkaConsumerHealthCheckIT extends KafkaHealthCheckTestSupport { + public static final String TOPIC = "test-health"; + public static final String SKIPPED_HEADER_KEY = "CamelSkippedHeader"; + public static final String PROPAGATED_CUSTOM_HEADER = "PropagatedCustomHeader"; + public static final byte[] PROPAGATED_HEADER_VALUE = "propagated header value".getBytes(); + + @Order(1) + @RegisterExtension + public static KafkaService service = KafkaServiceFactory.createService(); + @Order(2) + @RegisterExtension + public static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + protected static AdminClient kafkaAdminClient; + + private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerHealthCheckIT.class); + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + + @Override + public void configure() { + String from = "kafka:" + TOPIC + "?brokers=" + service.getBootstrapServers() + + "&groupId=KafkaConsumerHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor"; + + from(from) + .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) + .routeId("test-health-it").to(KafkaTestUtil.MOCK_RESULT); + } + }; + } + + @Override + public void configureContext(CamelContext context) { + // NO-OP + } + + @Order(1) + @Test + @DisplayName("Tests that liveness reports UP when it's actually up") + public void testReportUpWhenIsUp() { + // health-check liveness should be UP + CamelContext context = contextExtension.getContext(); + Collection res = HealthCheckHelper.invokeLiveness(context); + boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); + Assertions.assertTrue(up, "liveness check"); + } + + @Order(2) + @Test + @DisplayName("Tests that readiness reports UP when it's actually up") + public void testReportReadyWhenReady() { + CamelContext context = contextExtension.getContext(); + // health-check readiness should be ready + await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { + Collection res2 = HealthCheckHelper.invokeReadiness(context); + boolean up = res2.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); + Assertions.assertTrue(up, "readiness check"); + }); + } + + @Order(3) + @Test + @DisplayName("I/O test to ensure everything is working as expected") + public void testIO() throws InterruptedException { + to.expectedMessageCount(5); + to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); + to.expectedHeaderValuesReceivedInAnyOrder(KafkaConstants.LAST_RECORD_BEFORE_COMMIT, null, null, null, null, null); + to.expectedHeaderReceived(PROPAGATED_CUSTOM_HEADER, PROPAGATED_HEADER_VALUE); + + Properties props = KafkaTestUtil.getDefaultProperties(service); + try (KafkaProducer producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props)) { + for (int k = 0; k < 5; k++) { + String msg = "message-" + k; + ProducerRecord data = new ProducerRecord<>(TOPIC, "1", msg); + data.headers().add(new RecordHeader("CamelSkippedHeader", "skipped header value".getBytes())); + data.headers().add(new RecordHeader(PROPAGATED_CUSTOM_HEADER, PROPAGATED_HEADER_VALUE)); + producer.send(data); + } + } + + to.assertIsSatisfied(3000); + assertEquals(5, MockConsumerInterceptor.recordsCaptured.stream() + .flatMap(i -> StreamSupport.stream(i.records(TOPIC).spliterator(), false)).count()); + + Map headers = to.getExchanges().get(0).getIn().getHeaders(); + assertFalse(headers.containsKey(SKIPPED_HEADER_KEY), "Should not receive skipped header"); + assertTrue(headers.containsKey(PROPAGATED_CUSTOM_HEADER), "Should receive propagated header"); + } + + @Order(4) + @Test + @DisplayName("Tests that liveness reports UP when it's down") + public void testLivenessWhenDown() { + CamelContext context = contextExtension.getContext(); + // and shutdown Kafka which will make readiness report as DOWN + service.shutdown(); + + // health-check liveness should be UP + final Collection res = HealthCheckHelper.invokeLiveness(context); + final boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); + Assertions.assertTrue(up, "liveness check"); + } + + @Order(5) + @Test + @DisplayName("Tests that readiness reports down when it's actually down") + public void testReadinessWhenDown() { + // but health-check readiness should NOT be ready + await().atMost(20, TimeUnit.SECONDS).untilAsserted(this::readinessCheck); + } + + private void readinessCheck() { + CamelContext context = contextExtension.getContext(); + + Collection res2 = HealthCheckHelper.invoke(context); + Optional down + = res2.stream().filter(r -> r.getState().equals(HealthCheck.State.DOWN)).findFirst(); + Assertions.assertTrue(down.isPresent()); + String msg = down.get().getMessage().get(); + Assertions.assertTrue(msg.contains("KafkaConsumer is not ready")); + Map map = down.get().getDetails(); + Assertions.assertEquals(TOPIC, map.get("topic")); + Assertions.assertEquals("test-health-it", map.get("route.id")); + } +} diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerUnresolvableHealthCheckIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerUnresolvableHealthCheckIT.java index 5b6977e15bca6..38f6ee284cc84 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerUnresolvableHealthCheckIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaConsumerUnresolvableHealthCheckIT.java @@ -17,138 +17,55 @@ package org.apache.camel.component.kafka.integration.health; import java.util.Collection; -import java.util.Collections; -import java.util.Properties; import java.util.concurrent.TimeUnit; -import org.apache.camel.BindToRegistry; import org.apache.camel.CamelContext; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaComponent; -import org.apache.camel.component.kafka.MockConsumerInterceptor; -import org.apache.camel.component.kafka.integration.AbstractKafkaTestSupport; -import org.apache.camel.component.kafka.serde.DefaultKafkaHeaderDeserializer; -import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.health.HealthCheck; import org.apache.camel.health.HealthCheckHelper; -import org.apache.camel.health.HealthCheckRegistry; -import org.apache.camel.impl.health.DefaultHealthCheckRegistry; -import org.apache.camel.test.infra.kafka.services.KafkaService; -import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; -import org.apache.kafka.clients.admin.AdminClient; +import org.apache.camel.test.infra.core.ContextFixture; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - +@TestInstance(TestInstance.Lifecycle.PER_CLASS) @DisabledIfSystemProperty(named = "kafka.instance.type", matches = "local-strimzi-container", disabledReason = "Test infra Kafka runs the Strimzi containers in a way that conflicts with multiple concurrent images") -public class KafkaConsumerUnresolvableHealthCheckIT extends CamelTestSupport { +public class KafkaConsumerUnresolvableHealthCheckIT extends KafkaHealthCheckTestSupport { public static final String TOPIC = "test-health"; - public static KafkaService service = KafkaServiceFactory.createService(); - - protected static AdminClient kafkaAdminClient; - - private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerUnresolvableHealthCheckIT.class); - - @BindToRegistry("myHeaderDeserializer") - private MyKafkaHeaderDeserializer deserializer = new MyKafkaHeaderDeserializer(); - - @EndpointInject("kafka:" + TOPIC - + "?groupId=KafkaConsumerUnresolvableHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" - + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - @EndpointInject("mock:result") - private MockEndpoint to; - - private org.apache.kafka.clients.producer.KafkaProducer producer; - - @BeforeEach - public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); - producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); - MockConsumerInterceptor.recordsCaptured.clear(); - } - - @BeforeAll - public static void beforeClass() { - service.initialize(); - - LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); - System.setProperty("bootstrapServers", service.getBootstrapServers()); - System.setProperty("brokers", service.getBootstrapServers()); - } - - @AfterAll - public static void afterClass() { - service.shutdown(); - } - - @BeforeEach - public void setKafkaAdminClient() { - if (kafkaAdminClient == null) { - kafkaAdminClient = AbstractKafkaTestSupport.createAdminClient(service); - } - } - - @AfterEach - public void after() { - if (producer != null) { - producer.close(); - } - // clean all test topics - kafkaAdminClient.deleteTopics(Collections.singletonList(TOPIC)).all(); - } - @Override - protected CamelContext createCamelContext() throws Exception { - CamelContext context = super.createCamelContext(); + @ContextFixture + public void configureContext(CamelContext context) { context.getPropertiesComponent().setLocation("ref:prop"); KafkaComponent kafka = new KafkaComponent(context); kafka.init(); kafka.getConfiguration().setBrokers(service.getBootstrapServers().replace("localhost", "locaIhost")); context.addComponent("kafka", kafka); - - // install health check manually (yes a bit cumbersome) - HealthCheckRegistry registry = new DefaultHealthCheckRegistry(); - registry.setCamelContext(context); - Object hc = registry.resolveById("context"); - registry.register(hc); - hc = registry.resolveById("routes"); - registry.register(hc); - hc = registry.resolveById("consumers"); - registry.register(hc); - context.setExtension(HealthCheckRegistry.class, registry); - - return context; } @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { - @Override public void configure() { + String from = "kafka:" + TOPIC + + "?groupId=KafkaConsumerUnresolvableHealthCheckIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer&" + + "valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor"; + from(from) .process(exchange -> LOG.trace("Captured on the processor: {}", exchange.getMessage().getBody())) .routeId("test-health-it").to(to); @@ -158,22 +75,39 @@ public void configure() { @Order(1) @Test - public void kafkaConsumerHealthCheck() throws InterruptedException { + @DisplayName("Tests that liveness reports UP when it's actually up") + public void testReportUpWhenIsUp() { // health-check liveness should be UP + CamelContext context = contextExtension.getContext(); + Collection res = HealthCheckHelper.invokeLiveness(context); boolean up = res.stream().allMatch(r -> r.getState().equals(HealthCheck.State.UP)); Assertions.assertTrue(up, "liveness check"); + } + + @Order(2) + @Test + @DisplayName("Tests that readiness reports down when it's actually down") + public void testReportCorrectlyWhenDown() { + CamelContext context = contextExtension.getContext(); // health-check readiness should be down - await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { - Collection res2 = HealthCheckHelper.invokeReadiness(context); - boolean up2 = res2.stream().allMatch(r -> { - return r.getState().equals(HealthCheck.State.DOWN) && - r.getMessage().stream().allMatch(msg -> msg.contains("bootstrap")); - }); - Assertions.assertTrue(up2, "readiness check"); + await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> readinessCheck(context)); + } + + private static void readinessCheck(CamelContext context) { + Collection res2 = HealthCheckHelper.invokeReadiness(context); + boolean up2 = res2.stream().allMatch(r -> { + return r.getState().equals(HealthCheck.State.DOWN) && + r.getMessage().stream().allMatch(msg -> msg.contains("bootstrap")); }); + Assertions.assertTrue(up2, "readiness check"); + } + @Order(3) + @Test + @DisplayName("I/O test to ensure everything is working as expected") + public void kafkaConsumerHealthCheck() throws InterruptedException { String propagatedHeaderKey = "PropagatedCustomHeader"; byte[] propagatedHeaderValue = "propagated header value".getBytes(); to.expectedMessageCount(0); @@ -190,7 +124,4 @@ public void kafkaConsumerHealthCheck() throws InterruptedException { to.assertIsSatisfied(3000); } - - private static class MyKafkaHeaderDeserializer extends DefaultKafkaHeaderDeserializer { - } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaHealthCheckTestSupport.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaHealthCheckTestSupport.java new file mode 100644 index 0000000000000..e03514644c905 --- /dev/null +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/health/KafkaHealthCheckTestSupport.java @@ -0,0 +1,103 @@ +/* + * 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.kafka.integration.health; + +import java.util.Properties; + +import org.apache.camel.CamelContext; +import org.apache.camel.EndpointInject; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.component.kafka.MockConsumerInterceptor; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.health.HealthCheckRegistry; +import org.apache.camel.impl.health.DefaultHealthCheckRegistry; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.RouteFixture; +import org.apache.camel.test.infra.core.api.ConfigurableContext; +import org.apache.camel.test.infra.core.api.ConfigurableRoute; +import org.apache.camel.test.infra.kafka.services.KafkaService; +import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class KafkaHealthCheckTestSupport implements ConfigurableRoute, ConfigurableContext { + protected static final Logger LOG = LoggerFactory.getLogger(KafkaConsumerUnresolvableHealthCheckIT.class); + @Order(1) + @RegisterExtension + protected static KafkaService service = KafkaServiceFactory.createService(); + + @Order(2) + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); + protected org.apache.kafka.clients.producer.KafkaProducer producer; + @EndpointInject("mock:result") + protected MockEndpoint to; + + @BeforeAll + public static void beforeClass() { + service.initialize(); + + LOG.info("### Embedded Kafka cluster broker list: {}", service.getBootstrapServers()); + System.setProperty("bootstrapServers", service.getBootstrapServers()); + System.setProperty("brokers", service.getBootstrapServers()); + } + + @AfterEach + public void after() { + if (producer != null) { + producer.close(); + } + } + + @BeforeEach + public void before() { + Properties props = KafkaTestUtil.getDefaultProperties(service); + producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); + MockConsumerInterceptor.recordsCaptured.clear(); + } + + @ContextFixture + private void configureHealthRegistry(CamelContext context) { + // install health check manually (yes a bit cumbersome) + HealthCheckRegistry registry = new DefaultHealthCheckRegistry(); + registry.setCamelContext(context); + Object hc = registry.resolveById("context"); + registry.register(hc); + hc = registry.resolveById("routes"); + registry.register(hc); + hc = registry.resolveById("consumers"); + registry.register(hc); + context.setExtension(HealthCheckRegistry.class, registry); + } + + @Override + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + + protected abstract RoutesBuilder createRouteBuilder(); +} diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerCircuitBreakerIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerCircuitBreakerIT.java index 0bf9911001b64..a34869b258c5a 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerCircuitBreakerIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerCircuitBreakerIT.java @@ -25,15 +25,13 @@ import java.util.concurrent.atomic.LongAdder; import io.github.resilience4j.circuitbreaker.CircuitBreaker; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.RuntimeCamelException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.MockConsumerInterceptor; import org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener; -import org.apache.camel.component.kafka.integration.AbstractKafkaTestSupport; import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; @@ -59,46 +57,15 @@ public class KafkaPausableConsumerCircuitBreakerIT extends BaseEmbeddedKafkaTest private static final Logger LOG = LoggerFactory.getLogger(KafkaPausableConsumerCircuitBreakerIT.class); private static final int SIMULATED_FAILURES = 5; - private LongAdder count = new LongAdder(); - - @EndpointInject("kafka:" + SOURCE_TOPIC - + "?groupId=KafkaPausableConsumerCircuitBreakerIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; - - @EndpointInject("direct:intermediate") - private Endpoint intermediate; - - @EndpointInject("mock:result") - private MockEndpoint to; - + private static LongAdder count = new LongAdder(); + private static ScheduledExecutorService executorService; private org.apache.kafka.clients.producer.KafkaProducer producer; - private ScheduledExecutorService executorService; - - @BeforeEach - public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); - producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); - MockConsumerInterceptor.recordsCaptured.clear(); - } - - @AfterEach - public void after() { - if (producer != null) { - producer.close(); - } - // clean all test topics - AbstractKafkaTestSupport.createAdminClient(service) - .deleteTopics(Collections.singletonList(SOURCE_TOPIC)).all(); - } - /* * This is used by pausable to determine whether or not to pause. If returning true, processing continues. If * returning false, processing pauses. */ - private boolean canContinue() { + private static boolean canContinue() { // First one should go through ... if (count.intValue() <= 1) { LOG.info("Count is 1, allowing processing to proceed"); @@ -115,14 +82,31 @@ private boolean canContinue() { return false; } - public void increment() { + public static void increment() { count.increment(); } - public int getCount() { + public static int getCount() { return count.intValue(); } + @BeforeEach + public void before() { + Properties props = KafkaTestUtil.getDefaultProperties(service); + producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); + MockConsumerInterceptor.recordsCaptured.clear(); + } + + @AfterEach + public void after() { + if (producer != null) { + producer.close(); + } + // clean all test topics + KafkaTestUtil.createAdminClient(service) + .deleteTopics(Collections.singletonList(SOURCE_TOPIC)).all(); + } + @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @@ -159,15 +143,18 @@ public void configure() { // Binds the configuration to the registry getCamelContext().getRegistry().bind("pausableCircuit", circuitBreaker); - from(from) + from("kafka:" + SOURCE_TOPIC + + "?groupId=KafkaPausableConsumerCircuitBreakerIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") .pausable(new KafkaConsumerListener(), o -> canContinue()) .routeId("pausable-it") .process(exchange -> LOG.info("Got record from Kafka: {}", exchange.getMessage().getBody())) .circuitBreaker() .resilience4jConfiguration().circuitBreaker("pausableCircuit").end() - .to(intermediate); + .to("direct:intermediate"); - from(intermediate) + from("direct:intermediate") .process(exchange -> { LOG.info("Got record on the intermediate processor: {}", exchange.getMessage().getBody()); @@ -175,7 +162,7 @@ public void configure() { throw new RuntimeCamelException("Error"); } }) - .to(to) + .to(KafkaTestUtil.MOCK_RESULT) .end(); } }; @@ -190,6 +177,8 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { // Although all messages will be sent more than once to the exception only 5 messages should reach the final // destination, because sending them on the first few tries should fail + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); + to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); diff --git a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerIT.java b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerIT.java index 4a6d30debc8e0..1cc0e8d595212 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/component/kafka/integration/pause/KafkaPausableConsumerIT.java @@ -24,16 +24,14 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; -import org.apache.camel.Endpoint; -import org.apache.camel.EndpointInject; import org.apache.camel.RuntimeCamelException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.KafkaConstants; import org.apache.camel.component.kafka.MockConsumerInterceptor; import org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener; import org.apache.camel.component.kafka.consumer.support.ProcessingResult; -import org.apache.camel.component.kafka.integration.AbstractKafkaTestSupport; import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; +import org.apache.camel.component.kafka.integration.common.KafkaTestUtil; import org.apache.camel.component.mock.MockEndpoint; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; @@ -54,52 +52,35 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class KafkaPausableConsumerIT extends BaseEmbeddedKafkaTestSupport { - // Just a wrapper for us to check if the expected methods are being called - private static class TestListener extends KafkaConsumerListener { - volatile boolean afterConsumeCalled; - volatile boolean afterProcessCalled; - - @Override - public boolean afterConsume(Object ignored) { - afterConsumeCalled = true; - return super.afterConsume(ignored); - } - - @Override - public boolean afterProcess(ProcessingResult result) { - afterProcessCalled = true; - return super.afterProcess(result); - } - } - public static final String SOURCE_TOPIC = "pause-source"; - private static final Logger LOG = LoggerFactory.getLogger(KafkaPausableConsumerIT.class); - private static final int RETRY_COUNT = 10; - private LongAdder count = new LongAdder(); - - @EndpointInject("kafka:" + SOURCE_TOPIC - + "?groupId=KafkaPausableConsumerIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" - + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") - private Endpoint from; + private static LongAdder count = new LongAdder(); + private static TestListener testConsumerListener = new TestListener(); + private org.apache.kafka.clients.producer.KafkaProducer producer; - @EndpointInject("direct:intermediate") - private Endpoint intermediate; + private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - @EndpointInject("mock:result") - private MockEndpoint to; + private static boolean canContinue() { + // First one should go through ... + if (count.intValue() <= 1) { + return true; + } - private org.apache.kafka.clients.producer.KafkaProducer producer; + if (count.intValue() >= RETRY_COUNT) { + return true; + } - private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + return false; + } - private TestListener testConsumerListener = new TestListener(); + public static int getCount() { + return count.intValue(); + } @BeforeEach public void before() { - Properties props = AbstractKafkaTestSupport.getDefaultProperties(service); + Properties props = KafkaTestUtil.getDefaultProperties(service); producer = new org.apache.kafka.clients.producer.KafkaProducer<>(props); MockConsumerInterceptor.recordsCaptured.clear(); @@ -112,46 +93,32 @@ public void after() { producer.close(); } // clean all test topics - AbstractKafkaTestSupport.createAdminClient(service) + KafkaTestUtil.createAdminClient(service) .deleteTopics(Collections.singletonList(SOURCE_TOPIC)).all(); executorService.shutdownNow(); } - private boolean canContinue() { - // First one should go through ... - if (count.intValue() <= 1) { - return true; - } - - if (count.intValue() >= RETRY_COUNT) { - return true; - } - - return false; - } - public void increment() { count.increment(); } - public int getCount() { - return count.intValue(); - } - @Override protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from(from) + from("kafka:" + SOURCE_TOPIC + + "?groupId=KafkaPausableConsumerIT&autoOffsetReset=earliest&keyDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&valueDeserializer=org.apache.kafka.common.serialization.StringDeserializer" + + "&autoCommitIntervalMs=1000&pollTimeoutMs=1000&autoCommitEnable=true&interceptorClasses=org.apache.camel.component.kafka.MockConsumerInterceptor") .pausable(testConsumerListener, o -> canContinue()) .routeId("pausable-it") .process(exchange -> LOG.info("Got record from Kafka: {}", exchange.getMessage().getBody())) - .to(intermediate); + .to("direct:intermediate"); - from(intermediate) + from("direct:intermediate") .process(exchange -> { LOG.info("Got record on the intermediate processor: {}", exchange.getMessage().getBody()); @@ -159,7 +126,7 @@ public void configure() { throw new RuntimeCamelException("Error"); } }) - .to(to); + .to(KafkaTestUtil.MOCK_RESULT); } }; } @@ -173,6 +140,8 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { // Although all messages will be sent more than once to the exception only 5 messages should reach the final // destination, because sending them on the first few tries should fail + + MockEndpoint to = contextExtension.getMockEndpoint(KafkaTestUtil.MOCK_RESULT); to.expectedMessageCount(5); to.expectedBodiesReceivedInAnyOrder("message-0", "message-1", "message-2", "message-3", "message-4"); @@ -205,4 +174,22 @@ public void kafkaMessageIsConsumedByCamel() throws InterruptedException { assertFalse(headers.containsKey(skippedHeaderKey), "Should not receive skipped header"); assertTrue(headers.containsKey(propagatedHeaderKey), "Should receive propagated header"); } + + // Just a wrapper for us to check if the expected methods are being called + private static class TestListener extends KafkaConsumerListener { + volatile boolean afterConsumeCalled; + volatile boolean afterProcessCalled; + + @Override + public boolean afterConsume(Object ignored) { + afterConsumeCalled = true; + return super.afterConsume(ignored); + } + + @Override + public boolean afterProcess(ProcessingResult result) { + afterProcessCalled = true; + return super.afterProcess(result); + } + } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryEagerIT.java b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryEagerIT.java index e95a0f0642ebb..fd2584c417fc9 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryEagerIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryEagerIT.java @@ -18,36 +18,27 @@ import java.util.UUID; +import org.apache.camel.BindToRegistry; import org.apache.camel.CamelExecutionException; -import org.apache.camel.EndpointInject; -import org.apache.camel.RoutesBuilder; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test for eager idempotentRepository usage. */ -@TestInstance(TestInstance.Lifecycle.PER_METHOD) -public class KafkaIdempotentRepositoryEagerIT extends BaseEmbeddedKafkaTestSupport { +public class KafkaIdempotentRepositoryEagerIT extends SimpleIdempotentTest { - private KafkaIdempotentRepository kafkaIdempotentRepository; - - @EndpointInject("mock:out") - private MockEndpoint mockOut; - - @EndpointInject("mock:before") - private MockEndpoint mockBefore; + @BindToRegistry("kafkaIdempotentRepositoryEager") + private KafkaIdempotentRepository idempotentRepository + = new KafkaIdempotentRepository("TEST_EAGER_" + UUID.randomUUID(), service.getBootstrapServers()); @Override - protected RoutesBuilder createRouteBuilder() { + protected RouteBuilder createRouteBuilder() { // Every instance of the repository must use a different topic to guarantee isolation between tests - kafkaIdempotentRepository = new KafkaIdempotentRepository("TEST_EAGER_" + UUID.randomUUID(), getBootstrapServers()); - context.getRegistry().bind("kafkaIdempotentRepositoryEager", kafkaIdempotentRepository); return new RouteBuilder() { @Override @@ -60,16 +51,22 @@ public void configure() { @Test public void testRemovesDuplicates() { + ProducerTemplate template = contextExtension.getProducerTemplate(); + for (int i = 0; i < 10; i++) { template.sendBodyAndHeader("direct:in", "Test message", "id", i % 5); } + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); assertEquals(5, mockOut.getReceivedCounter()); + + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); assertEquals(10, mockBefore.getReceivedCounter()); } @Test public void testRollsBackOnException() { + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); mockOut.whenAnyExchangeReceived(exchange -> { int id = exchange.getIn().getHeader("id", Integer.class); if (id == 0) { @@ -77,6 +74,7 @@ public void testRollsBackOnException() { } }); + ProducerTemplate template = contextExtension.getProducerTemplate(); for (int i = 0; i < 10; i++) { try { template.sendBodyAndHeader("direct:in", "Test message", "id", i % 5); @@ -85,10 +83,11 @@ public void testRollsBackOnException() { } } - assertEquals(6, mockOut.getReceivedCounter()); // id{0} goes through the - // idempotency check - // twice - assertEquals(10, mockBefore.getReceivedCounter()); + assertEquals(5, mockOut.getReceivedCounter(), + "Only the 5 messages from the previous test should have been received "); + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); + assertEquals(20, mockBefore.getReceivedCounter(), + "Test should have received 20 messages in total from all the tests"); } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryNonEagerIT.java b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryNonEagerIT.java index 4df97548cdf0b..cb5b5da2757b9 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryNonEagerIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryNonEagerIT.java @@ -19,14 +19,19 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +import org.apache.camel.BindToRegistry; +import org.apache.camel.CamelContext; import org.apache.camel.CamelExecutionException; -import org.apache.camel.EndpointInject; -import org.apache.camel.RoutesBuilder; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; +import org.apache.camel.component.kafka.KafkaComponent; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.ContextFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,46 +39,57 @@ /** * Test for non-eager idempotentRepository usage. */ -@TestInstance(TestInstance.Lifecycle.PER_METHOD) -public class KafkaIdempotentRepositoryNonEagerIT extends BaseEmbeddedKafkaTestSupport { +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class KafkaIdempotentRepositoryNonEagerIT extends SimpleIdempotentTest { - private KafkaIdempotentRepository kafkaIdempotentRepository; + @BindToRegistry("kafkaIdempotentRepositoryNonEager") + private KafkaIdempotentRepository kafkaIdempotentRepository + = new KafkaIdempotentRepository("TEST_NON_EAGER_" + UUID.randomUUID(), service.getBootstrapServers()); - @EndpointInject("mock:out") - private MockEndpoint mockOut; + @ContextFixture + public void configureKafka(CamelContext context) { + context.getPropertiesComponent().setLocation("ref:prop"); - @EndpointInject("mock:before") - private MockEndpoint mockBefore; + KafkaComponent kafka = new KafkaComponent(context); + kafka.init(); + kafka.getConfiguration().setBrokers(service.getBootstrapServers()); + context.addComponent("kafka", kafka); + } @Override - protected RoutesBuilder createRouteBuilder() { - // Every instance of the repository must use a different topic to guarantee isolation between tests - kafkaIdempotentRepository = new KafkaIdempotentRepository("TEST_NON_EAGER_" + UUID.randomUUID(), getBootstrapServers()); - context.getRegistry().bind("kafkaIdempotentRepositoryNonEager", kafkaIdempotentRepository); - + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - from("direct:in").to("mock:before").idempotentConsumer(header("id")) - .idempotentRepository("kafkaIdempotentRepositoryNonEager").eager(false).to("mock:out").end(); + from("direct:in").to("mock:before") + .idempotentConsumer(header("id")).idempotentRepository("kafkaIdempotentRepositoryNonEager").eager(false) + .to("mock:out").end(); } }; } + @Order(1) @Test + @DisplayName("Tests that duplicated messages do not go through") public void testRemovesDuplicates() { + ProducerTemplate template = contextExtension.getProducerTemplate(); for (int i = 0; i < 10; i++) { template.sendBodyAndHeader("direct:in", "Test message", "id", i % 5); } + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); await().atMost(5, TimeUnit.SECONDS) .untilAsserted(() -> assertEquals(5, mockOut.getReceivedCounter())); + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); assertEquals(10, mockBefore.getReceivedCounter()); } + @Order(2) @Test + @DisplayName("Tests that processing exceptions cause the message to be rolled back") public void testRollsBackOnException() { + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); mockOut.whenAnyExchangeReceived(exchange -> { int id = exchange.getIn().getHeader("id", Integer.class); if (id == 0) { @@ -81,6 +97,7 @@ public void testRollsBackOnException() { } }); + ProducerTemplate template = contextExtension.getProducerTemplate(); for (int i = 0; i < 10; i++) { try { template.sendBodyAndHeader("direct:in", "Test message", "id", i % 5); @@ -89,10 +106,12 @@ public void testRollsBackOnException() { } } - assertEquals(6, mockOut.getReceivedCounter()); // id{0} goes through the - // idempotency check - // twice - assertEquals(10, mockBefore.getReceivedCounter()); + assertEquals(5, mockOut.getReceivedCounter(), + "Only the 5 messages from the previous test should have been received "); + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); + + assertEquals(20, mockBefore.getReceivedCounter(), + "Test should have received 20 messages in total from all the tests"); } } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryPersistenceIT.java b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryPersistenceIT.java index 4ad1a0f57b738..0c76cedf03c86 100644 --- a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryPersistenceIT.java +++ b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/KafkaIdempotentRepositoryPersistenceIT.java @@ -21,11 +21,13 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import org.apache.camel.EndpointInject; -import org.apache.camel.RoutesBuilder; +import org.apache.camel.CamelContext; +import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kafka.integration.BaseEmbeddedKafkaTestSupport; import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.infra.core.ContextFixture; +import org.apache.camel.test.infra.core.api.ConfigurableContext; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -50,26 +52,24 @@ * annotations. */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@TestInstance(TestInstance.Lifecycle.PER_METHOD) -public class KafkaIdempotentRepositoryPersistenceIT extends BaseEmbeddedKafkaTestSupport { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class KafkaIdempotentRepositoryPersistenceIT extends BaseEmbeddedKafkaTestSupport implements ConfigurableContext { private KafkaIdempotentRepository kafkaIdempotentRepository; - @EndpointInject("mock:out") - private MockEndpoint mockOut; - - @EndpointInject("mock:before") - private MockEndpoint mockBefore; - void clearTopics() { kafkaAdminClient.deleteTopics(Arrays.asList("TEST_PERSISTENCE")).all(); } @Override - protected RoutesBuilder createRouteBuilder() { + @ContextFixture + public void configureContext(CamelContext context) { kafkaIdempotentRepository = new KafkaIdempotentRepository("TEST_PERSISTENCE", getBootstrapServers()); context.getRegistry().bind("kafkaIdempotentRepositoryPersistence", kafkaIdempotentRepository); + } + @Override + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { @Override public void configure() { @@ -80,6 +80,8 @@ public void configure() { } private void sendMessages(long count) { + ProducerTemplate template = contextExtension.getProducerTemplate(); + for (int i = 0; i < count; i++) { template.sendBodyAndHeader("direct:in", "Test message", "id", i % 5); } @@ -94,10 +96,12 @@ public void testFirstPassFiltersAsExpected() { sendMessages(count); // all records sent initially + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); await().atMost(10, TimeUnit.SECONDS) .untilAsserted(() -> assertEquals(count, mockBefore.getReceivedCounter())); // only first 5 records are received, the rest are filtered + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); assertEquals(5, mockOut.getReceivedCounter()); } @@ -111,10 +115,12 @@ public void testSecondPassFiltersEverything() { sendMessages(count); // all records sent initially + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); await().atMost(10, TimeUnit.SECONDS) .untilAsserted(() -> assertEquals(count, mockBefore.getReceivedCounter())); // nothing pass the idempotent consumer this time + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); assertEquals(0, mockOut.getReceivedCounter()); } @@ -130,10 +136,12 @@ public void testThirdPassFiltersEverything(long count, long passes) { } // all records sent initially + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); await().atMost(10, TimeUnit.SECONDS) .untilAsserted(() -> assertEquals(count * passes, mockBefore.getReceivedCounter())); // nothing gets passed the idempotent consumer this time + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); assertEquals(0, mockOut.getReceivedCounter()); } @@ -149,16 +157,19 @@ private static Stream multiplePassesProvider() { @Test @DisplayName("Checks that the remaining messages can finally go through") public void testFourthPass() { + ProducerTemplate template = contextExtension.getProducerTemplate(); int count = 5; for (int i = 5; i < 10; i++) { template.sendBodyAndHeader("direct:in", "Test message", "id", i); } // all records sent initially + MockEndpoint mockBefore = contextExtension.getMockEndpoint("mock:before"); await().atMost(10, TimeUnit.SECONDS) .untilAsserted(() -> assertEquals(count, mockBefore.getReceivedCounter())); // there are no duplicate messages on this run so all of them should pass + MockEndpoint mockOut = contextExtension.getMockEndpoint("mock:out"); assertEquals(count, mockOut.getReceivedCounter()); } @@ -171,5 +182,4 @@ public void testClear() { clearTopics(); } - } diff --git a/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/SimpleIdempotentTest.java b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/SimpleIdempotentTest.java new file mode 100644 index 0000000000000..265c62cdd8040 --- /dev/null +++ b/components/camel-kafka/src/test/java/org/apache/camel/processor/idempotent/kafka/SimpleIdempotentTest.java @@ -0,0 +1,47 @@ +/* + * 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.processor.idempotent.kafka; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.infra.core.CamelContextExtension; +import org.apache.camel.test.infra.core.DefaultCamelContextExtension; +import org.apache.camel.test.infra.core.DefaultContextLifeCycleManager; +import org.apache.camel.test.infra.core.RouteFixture; +import org.apache.camel.test.infra.kafka.services.KafkaService; +import org.apache.camel.test.infra.kafka.services.KafkaServiceFactory; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.extension.RegisterExtension; + +public abstract class SimpleIdempotentTest { + @Order(1) + @RegisterExtension + protected static KafkaService service = KafkaServiceFactory.createSingletonService(); + + @Order(2) + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension( + new DefaultContextLifeCycleManager(DefaultContextLifeCycleManager.DEFAULT_SHUTDOWN_TIMEOUT, false)); + + @RouteFixture + public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(createRouteBuilder()); + } + + protected abstract RouteBuilder createRouteBuilder(); +} diff --git a/docs/user-manual/modules/ROOT/pages/test-infra.adoc b/docs/user-manual/modules/ROOT/pages/test-infra.adoc index 8c4135a36ff72..d3300b1370a7d 100644 --- a/docs/user-manual/modules/ROOT/pages/test-infra.adoc +++ b/docs/user-manual/modules/ROOT/pages/test-infra.adoc @@ -1,5 +1,71 @@ = Test Infrastructure +The components in the Camel Test Infra provide utilities to simplify testing with Camel and other systems may interact with it. They work as JUnit 5 extensions. + +== Working with the Camel Context in Tests + +When testing Camel or a Camel-based integration, you almost certainly need to use the `CamelContext` to configure the registry, add routes and execute other operations. The test infra comes with a module that provides a JUnit 5 extension that allows you to inject a Camel context into your tests. + +Adding it to your test code is as simple as adding the following lines of code to your test class: + +[source,java] +---- +@RegisterExtension +protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); +---- + +Then, via the extension, you can access the context (ie.: ```contextExtension.getContext()`) to manipulate it as need in the tests. + +The extension comes with a few utilities to simplify configuring the context, and adding routes at the appropriate time. + +=== Configuring the Camel Context + +To create a method that configures the context, you can declare a method receiving an instance of `CamelContext` and annotate it with `@ContextFixture`. + +[source,java] +---- +@ContextFixture +public void configureContext(CamelContext context) { + // context configuration code here +} +---- + +Additionally, you can simplify the class hierarchy, and ensure consistency you may also implement the `ConfigurableContext` interface. + +=== Configuring the Routes + +You can configure the routes using a similar process as the one described for configuring the Camel context. You can create a method that receives an instance of `CamelContext` and annotate it with `@RouteFixture`. + +[source,java] +---- +@RouteFixture +public void createRouteBuilder(CamelContext context) throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from(fromUri).to(destUri); + } + }); +} +---- + +=== Use the Camel Context Extension + +To start using the Camel Context extension on your code, add the following dependency: + +[source,xml] +---- + + org.apache.camel + camel-test-infra-core + ${camel.version} + test + test-jar + +---- + +For simplicity and consistency, you may also declare the route as implementing the `ConfigurableRoute`. + == Simulating the Test Infrastructure One of the first steps when implementing a new test, is to identify how to simulate infrastructure required for it to @@ -10,7 +76,13 @@ and uses container images to simulate the environments. Additionally, it may als environments as well as, when available, embeddable components. This varies by each component, and it is recommended to check the code for additional details. -== Writing A New Test Infrastructure Module +=== Writing A New Test Infrastructure Module + + +[NOTE] +==== +This section is aimed at Camel maintainers that need to write new test infra components. End users can skip this section. +==== The test code abstracts the provisioning of test environments behind service classes (i.e.: JMSService, JDBCService, etc). The purpose of the service class is to abstract the both the type service (i.e.: Kafka, Strimzi, etc) and @@ -31,7 +103,7 @@ whenever possible. It is also possible to use embeddable components when required, although this usually lead to more code and higher maintenance. -=== Recommended Structure for Test Infrastructure Modules +==== Recommended Structure for Test Infrastructure Modules The service should provide an interface, named after the infrastructure being implemented, and this interface should extend the https://github.com/apache/camel/blob/main/test-infra/camel-test-infra-common/src/test/java/org/apache/camel/test/infra/common/services/TestService.java[TestService] @@ -41,9 +113,12 @@ and https://junit.org/junit5/docs/5.1.1/api/org/junit/jupiter/api/extension/Afte should be the preferred extensions whenever possible because they allow the instance of the infrastructure to be static throughout the test execution. -*Note*: bear in mind that, according to the https://junit.org/junit5/docs/5.1.1/api/org/junit/jupiter/api/extension/RegisterExtension.html[JUnit 5 extension] +[NOTE] +==== +Bear in mind that, according to the https://junit.org/junit5/docs/5.1.1/api/org/junit/jupiter/api/extension/RegisterExtension.html[JUnit 5 extension] model, the time of initialization of the service may differ depending on whether the service instance is declared as static or not in the test class. As such, the code should make no assumptions as to its time of initialization. +==== Ideally, there should be two concrete implementations of the services: one of the remote service (if applicable) and another for the container service: @@ -66,7 +141,7 @@ as `.`. More complex services may use the builder available thr the service accordingly. -=== Registering Properties +==== Registering Properties All services should register the properties, via `System.setProperty` that allow access to the services. This is required in order to resolve those properties when running tests using the Spring framework. This registration allows the properties @@ -74,11 +149,12 @@ to be resolved in Spring's XML files. This registration is done in the `registerProperties` methods during the service initialization. -=== Registering Properties Example: +==== Registering Properties Example: -Registering the properties in the concrete service implementation: +Registering the properties in the concrete service implementation: -``` +[source,java] +---- public void registerProperties() { // MyServiceProperties.MY_SERVICE_HOST is a string with value "my.service.host" System.setProperty(MyServiceProperties.MY_SERVICE_HOST, container.getHost()); @@ -97,25 +173,26 @@ Registering the properties in the concrete service implementation: registerProperties(); LOG.info("MyService instance running at {}", getServiceAddress()); } -``` +---- Then, when referring these properties in Camel routes or Spring XML properties, you may use `{{my.service.host}}`, `{{my.service.port}}` and `{{my.service.address}}`. -=== Packaging Recommendations +==== Packaging Recommendations This is test infrastructure code, therefore it should be package as test type artifacts. The https://github.com/apache/camel/blob/main/test-infra/camel-test-infra-parent[parent pom] should provide all the necessary bits for packaging the test infrastructure. -== Using The New Test Infrastructure +=== Using The New Test Infrastructure -Using the test infra in a new component test is rather straightforward similar to using any other reusable component. +Using the test infra in a new component test is rather straightforward, similar to using any other reusable component. You start by declaring the test infra dependencies in your pom file. This should be similar to: -```xml +[source,xml] +---- org.apache.camel @@ -124,22 +201,27 @@ This should be similar to: test-jar test -``` +---- -*Note*: on the dependencies above, the dependency version is set to `${project.version}`. This should be adjusted to the +[NOTE] +==== +On the dependencies above, the dependency version is set to `${project.version}`. This should be adjusted to the Camel version when used outside the Camel Core project. +==== On the test class, add a member variable for the service and annotate it with the https://junit.org/junit5/docs/5.1.1/api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension], -in order to let JUnit 5 manage its lifecycle. +in order to let JUnit 5 manage its lifecycle. -``` +[source,java] +---- @RegisterExtension static MyService service = MyServiceServiceFactory.createService(); -``` +---- -More complex test services can be created using something similar to: +More complex test services can be created using something similar to: -``` +[source,java] +---- @RegisterExtension static MyService service = MyServiceServiceFactory .builder() @@ -147,19 +229,21 @@ static MyService service = MyServiceServiceFactory .addLocalMapping(MyTestClass::staticMethodReturningAService) // sets the handler for -Dmy-service.instance.type=local-myservice-local-container .addMapping("local-alternative-service", MyTestClass::anotherMethodReturningAService) // sets the handler for -Dmy-service.instance.type=local-alternative-service .createService(); -``` +---- You can use the methods as well as the registered properties to access the test infrastructure services available. -When using these properties in Spring XML files, you may use those properties. +When using these properties in Spring XML files, you may use those properties. -``` +[source,xml] +---- -``` +---- It's also possible to use these properties in the test code itself. For example, when setting up the test url for the Camel component: -``` +[source,java] +---- protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { public void configure() { @@ -168,11 +252,34 @@ Camel component: } }; } -``` +---- + +==== Execution Ordering + +When combining the different modules of the test infra, you may need to ensure that they execute in the proper order. You can do so by using JUnit's `@Order` annotation. + +For instance: + +[source,java] +---- + @Order(1) + @RegisterExtension + protected static KafkaService service = KafkaServiceFactory.createSingletonService(); + + @Order(2) + @RegisterExtension + protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension(); +---- == Converting Camel TestContainers Code To The New Test Infrastructure + +[NOTE] +==== +This section is aimed at Camel maintainers that need to write new test infra components. End users can skip this section. +==== + Using the camel-nats as an example, we can compare how the base test class for nats changed between https://github.com/apache/camel/blob/camel-3.6.0/components/camel-nats/src/test/java/org/apache/camel/component/nats/NatsTestSupport.java[3.6.x] and https://github.com/apache/camel/blob/camel-3.7.0/components/camel-nats/src/test/java/org/apache/camel/component/nats/NatsTestSupport.java[3.7.x]. diff --git a/docs/user-manual/modules/ROOT/pages/testing.adoc b/docs/user-manual/modules/ROOT/pages/testing.adoc index c126571cad993..10cec2e6b7712 100644 --- a/docs/user-manual/modules/ROOT/pages/testing.adoc +++ b/docs/user-manual/modules/ROOT/pages/testing.adoc @@ -20,14 +20,14 @@ The following modules are supported: |======================================================================= |Component |Description -|xref:components:others:test-junit5.adoc[camel-test-junit5] |*JUnit 5*: Is a standalone Java +|xref:components:others:test-junit5.adoc[camel-test-junit5] |*JUnit 5*: Is an older standalone Java library letting you easily create Camel test cases using a single Java class for all your configuration and routing without. |xref:components:others:test-main-junit5.adoc[camel-test-main-junit5] | *JUnit 5*: Used for testing Camel in Camel Main mode |xref:components:others:test-spring-junit5.adoc[camel-test-spring-junit5] | *JUnit 5*: Used for testing Camel with Spring / Spring Boot -|xref:test-infra.adoc[camel-test-infra] | *Camel Test Infra*: Camel Test Infra is a set of modules that leverage Test Containers and abstract the provisioning and execution of test infrastructure. It is the successor of the camel-testcontainers components. +|xref:test-infra.adoc[camel-test-infra] | *Camel Test Infra*: Camel Test Infra is a set of modules that leverage modern JUnit 5 features to abstract the provisioning and execution of test infrastructure. Among other things, it provides abstraction of the infrastructure (based on Test Containers - being the de-facto successor of the camel-testcontainers components) as well as JUnit 5 extensions for the Camel Context itself. |======================================================================= diff --git a/test-infra/camel-test-infra-core/pom.xml b/test-infra/camel-test-infra-core/pom.xml new file mode 100644 index 0000000000000..4ffa7507ec2a3 --- /dev/null +++ b/test-infra/camel-test-infra-core/pom.xml @@ -0,0 +1,80 @@ + + + + + 4.0.0 + + org.apache.camel + test-infra + 4.0.0-SNAPSHOT + + + camel-test-infra-core + jar + Camel :: Test Infra :: Core + Core testing infrastructure for Camel components + + + 4.0.0 + + + + + + org.apache.camel + camel-core + test + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + test-compile + + test-jar + + + + + + + diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java new file mode 100644 index 0000000000000..1ddceac2b9868 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/CamelContextExtension.java @@ -0,0 +1,68 @@ +/* + * 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.test.infra.core; + +import org.apache.camel.CamelContext; +import org.apache.camel.ConsumerTemplate; +import org.apache.camel.NoSuchEndpointException; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; + +/** + * A JUnit 5 extension that allows you to include a {@link CamelContext} in your test code. + */ +public interface CamelContextExtension extends BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback { + + /** + * Gets the {@link CamelContext} created by this extension + * @return an instance of the Camel context to use in the test + */ + CamelContext getContext(); + + /** + * Creates a {@link ProducerTemplate} from the context + * @return an instance of a producer template to use with the test + */ + ProducerTemplate getProducerTemplate(); + + /** + * Creates a {@link ConsumerTemplate} from the context + * @return an instance of a consumer template to use with the test + */ + ConsumerTemplate getConsumerTemplate(); + + /** + * Gets a {@link MockEndpoint} for the given URI. If the endpoint does not exist, it will be created + * @param uri the URI to create the mock to + * @return a Mock endpoint instance for the given URI + */ + MockEndpoint getMockEndpoint(String uri); + + /** + * Gets a {@link MockEndpoint} for the given URI. + * @param uri the URI to create the mock to + * @param create whether to create the endpoint if it does not exist + * @return a Mock endpoint instance for the given URI + * @throws NoSuchEndpointException if the endpoint does not exist and a new one should not be created + */ + MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException; +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java new file mode 100644 index 0000000000000..b92f713652592 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextFixture.java @@ -0,0 +1,35 @@ +/* + * 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.test.infra.core; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates the need for configuring Camel contexts. Use it to annotate methods that configure the context. + *

+ * The signature for such methods should be: public void configureContext(CamelContext context). + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ContextFixture { +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java new file mode 100644 index 0000000000000..12805a015a73f --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/ContextLifeCycleManager.java @@ -0,0 +1,51 @@ +/* + * 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.test.infra.core; + +import org.apache.camel.CamelContext; + +/** + * A life cycle manager for the Camel instance manages how the context is started, stopped and configured based on which + * phase of the test is being executed. + */ +public interface ContextLifeCycleManager { + + /** + * A hook to execute after all tests are executed + * @param context the context instance + */ + void afterAll(CamelContext context); + + /** + * A hook to execute before all tests are executed + * @param context the context instance + */ + void beforeAll(CamelContext context); + + /** + * A hook to execute after each test is executed + * @param context the context instance + */ + void afterEach(CamelContext context); + + /** + * A hook to execute before each test is executed + * @param context the context instance + */ + void beforeEach(CamelContext context); +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java new file mode 100644 index 0000000000000..7063ab210b135 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultCamelContextExtension.java @@ -0,0 +1,282 @@ +/* + * 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.test.infra.core; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.CamelContext; +import org.apache.camel.ConsumerTemplate; +import org.apache.camel.Endpoint; +import org.apache.camel.EndpointInject; +import org.apache.camel.NoSuchEndpointException; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.util.URISupport; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * A simple Camel context extension suitable for most of the simple use cases in Camel and end-user applications. + */ +public class DefaultCamelContextExtension implements CamelContextExtension { + private static final Logger LOG = LoggerFactory.getLogger(DefaultCamelContextExtension.class); + private final ContextLifeCycleManager lifeCycleManager; + private CamelContext context; + private ProducerTemplate producerTemplate; + private ConsumerTemplate consumerTemplate; + + /** + * Creates a new instance of the extension + */ + public DefaultCamelContextExtension() { + this(new DefaultContextLifeCycleManager()); + } + + /** + * Creates a new instance of the extension with a custom {@link ContextLifeCycleManager} + * + * @param lifeCycleManager a life cycle manager for the context + */ + public DefaultCamelContextExtension(ContextLifeCycleManager lifeCycleManager) { + this.lifeCycleManager = lifeCycleManager; + } + + /** + * Resolves an endpoint and asserts that it is found. + */ + public static T resolveMandatoryEndpoint(CamelContext context, String endpointUri, Class endpointType) { + T endpoint = context.getEndpoint(endpointUri, endpointType); + + assertNotNull(endpoint, "No endpoint found for URI: " + endpointUri); + + return endpoint; + } + + private static String commonFixtureMessage(Class annotationClass, Object instance) { + return "Unable to setup fixture " + annotationClass.getSimpleName() + " on " + instance.getClass().getName(); + } + + private static String invocationTargetMessage(Class annotationClass, Object instance, String methodName) { + return commonFixtureMessage(annotationClass, instance) + " due to invocation target exception to method: " + methodName; + } + + private static String illegalAccessMessage(Class annotationClass, Object instance, String methodName) { + return commonFixtureMessage(annotationClass, instance) + " due to illegal access to method: " + methodName; + } + + protected CamelContext createCamelContext() { + return new DefaultCamelContext(); + } + + @Override + public void afterAll(ExtensionContext extensionContext) throws Exception { + lifeCycleManager.afterAll(context); + } + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + context = createCamelContext(); + + producerTemplate = context.createProducerTemplate(); + producerTemplate.start(); + + consumerTemplate = context.createConsumerTemplate(); + consumerTemplate.start(); + + lifeCycleManager.beforeAll(context); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + final Object o = extensionContext.getTestInstance().get(); + + LOG.info("********************************************************************************"); + LOG.info("Testing: {} ({})", extensionContext.getDisplayName(), o.getClass().getName()); + + setupFixtureFields(extensionContext, EndpointInject.class, o); + setupFixtureFields(extensionContext, BindToRegistry.class, o); + + if (!context.isStarted()) { + setupFixture(extensionContext, ContextFixture.class, o); + setupFixture(extensionContext, RouteFixture.class, o); + + lifeCycleManager.beforeEach(context); + } + } + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + lifeCycleManager.afterEach(context); + + final Object o = extensionContext.getTestInstance().get(); + LOG.info("Testing done: {} ({})", extensionContext.getDisplayName(), o.getClass().getName()); + LOG.info("********************************************************************************"); + } + + private void setupFixture(ExtensionContext extensionContext, Class annotationClass, Object instance) { + final Class testClass = extensionContext.getTestClass().get(); + + Arrays.stream(testClass.getMethods()).filter(m -> m.isAnnotationPresent(annotationClass)).forEach(m -> doInvokeFixture(annotationClass, instance, m)); + } + + private void setupFixtureFields(ExtensionContext extensionContext, Class annotationClass, Object instance) { + final Class testClass = extensionContext.getTestClass().get(); + + var superClass = testClass.getSuperclass(); + while (superClass != null) { + Arrays.stream(superClass.getDeclaredFields()).filter(m -> m.isAnnotationPresent(annotationClass)).forEach(f -> doInvokeFixture(f.getAnnotation(annotationClass), instance, f)); + + superClass = superClass.getSuperclass(); + } + + Arrays.stream(testClass.getDeclaredFields()).filter(f -> f.isAnnotationPresent(annotationClass)).forEach(f -> doInvokeFixture(f.getAnnotation(annotationClass), instance, f)); + } + + private void doInvokeFixture(Class annotationClass, Object instance, Method m) { + var methodName = m.getName(); + LOG.trace("Checking instance method: {}", methodName); + try { + m.invoke(instance, context); + } catch (IllegalAccessException e) { + throw new RuntimeException(illegalAccessMessage(annotationClass, instance, methodName), e); + } catch (InvocationTargetException e) { + throw new RuntimeException(invocationTargetMessage(annotationClass, instance, methodName), e); + } + } + + private void doInvokeFixture(Annotation annotation, Object instance, Field f) { + var fieldName = f.getName(); + LOG.trace("Checking instance field: {}", fieldName); + try { + if (!Modifier.isPublic(f.getModifiers())) { + f.setAccessible(true); + } + + if (annotation instanceof BindToRegistry r) { + String bindValue = r.value(); + + context.getRegistry().bind(bindValue, f.get(instance)); + + return; + } + + if (annotation instanceof EndpointInject e) { + String uri = e.value(); + + if (f.getType() == MockEndpoint.class) { + final MockEndpoint mockEndpoint = getMockEndpoint(uri); + + assert mockEndpoint != null; + + f.set(instance, mockEndpoint); + } else { + final Endpoint endpoint = context.getEndpoint(uri); + + assert endpoint != null; + f.set(instance, endpoint); + } + } + } catch (IllegalAccessException e) { + throw new RuntimeException(illegalAccessMessage(annotation.getClass(), instance, fieldName), e); + } + } + + @Override + public CamelContext getContext() { + return context; + } + + @Override + public ProducerTemplate getProducerTemplate() { + return producerTemplate; + } + + @Override + public ConsumerTemplate getConsumerTemplate() { + return consumerTemplate; + } + + /** + * Resolves a mandatory endpoint for the given URI and expected type or an exception is thrown + * + * @param uri the Camel URI to use to create or resolve an endpoint + * @param endpointType the endpoint type (i.e., its class) to resolve + * @return the endpoint + */ + protected T resolveMandatoryEndpoint(String uri, Class endpointType) { + return resolveMandatoryEndpoint(context, uri, endpointType); + } + + @Override + public MockEndpoint getMockEndpoint(String uri) { + MockEndpoint mock = getMockEndpoint(uri, true); + + return mock; + } + + @Override + public MockEndpoint getMockEndpoint(String uri, boolean create) throws NoSuchEndpointException { + // look for existing mock endpoints that have the same queue name, and + // to + // do that we need to normalize uri and strip out query parameters and + // whatnot + String n; + try { + n = URISupport.normalizeUri(uri); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + // strip query + int idx = n.indexOf('?'); + if (idx != -1) { + n = n.substring(0, idx); + } + final String target = n; + + // lookup endpoints in registry and try to find it + MockEndpoint found = (MockEndpoint) context.getEndpointRegistry().values().stream().filter(e -> e instanceof MockEndpoint).filter(e -> { + String t = e.getEndpointUri(); + // strip query + int idx2 = t.indexOf('?'); + if (idx2 != -1) { + t = t.substring(0, idx2); + } + return t.equals(target); + }).findFirst().orElse(null); + + if (found != null) { + return found; + } + + if (create) { + return resolveMandatoryEndpoint(uri, MockEndpoint.class); + } else { + throw new NoSuchEndpointException(String.format("MockEndpoint %s does not exist.", uri)); + } + } +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java new file mode 100644 index 0000000000000..7c395a473ea35 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/DefaultContextLifeCycleManager.java @@ -0,0 +1,68 @@ +/* + * 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.test.infra.core; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.mock.MockEndpoint; + +/** + * A default lifecycle manager suitable for most of the tests within Camel and end-user applications + */ +public class DefaultContextLifeCycleManager implements ContextLifeCycleManager { + public static final int DEFAULT_SHUTDOWN_TIMEOUT = 10; + private int shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT; + private boolean reset = true; + + /** + * Creates a new instance of this class + */ + public DefaultContextLifeCycleManager() { + } + + /** + * Creates a new instance of this class + * @param shutdownTimeout the shutdown timeout + * @param reset whether to reset any {@link MockEndpoint} after each test execution + */ + public DefaultContextLifeCycleManager(int shutdownTimeout, boolean reset) { + this.shutdownTimeout = shutdownTimeout; + this.reset = reset; + } + + @Override + public void afterAll(CamelContext context) { + context.shutdown(); + } + + @Override + public void beforeAll(CamelContext context) { + context.getShutdownStrategy().setTimeout(shutdownTimeout); + } + + @Override + public void afterEach(CamelContext context) { + if (reset) { + MockEndpoint.resetMocks(context); + } + } + + @Override + public void beforeEach(CamelContext context) { + context.start(); + } +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java new file mode 100644 index 0000000000000..a37cad638b748 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/RouteFixture.java @@ -0,0 +1,35 @@ +/* + * 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.test.infra.core; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates the need for configuring Camel routes to a context. Use it to annotate methods that configure the routes. + * + * The signature for such methods should be: public void configureRoute(CamelContext context). + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface RouteFixture { +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java new file mode 100644 index 0000000000000..b837f62ed861f --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableContext.java @@ -0,0 +1,35 @@ +/* + * 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.test.infra.core.api; + +import org.apache.camel.CamelContext; + +/** + * A utility interface to indicate classes that configure the context. Usage of this interface is optional, but + * recommended for consistency. + */ +public interface ConfigurableContext { + + /** + * Configures the context + * @param context the context to configure + * @throws Exception if the context cannot be configured + */ + void configureContext(CamelContext context) throws Exception; + +} diff --git a/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java new file mode 100644 index 0000000000000..2530675204190 --- /dev/null +++ b/test-infra/camel-test-infra-core/src/test/java/org/apache/camel/test/infra/core/api/ConfigurableRoute.java @@ -0,0 +1,34 @@ +/* + * 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.test.infra.core.api; + +import org.apache.camel.CamelContext; + +/** + * A utility interface to indicate classes that configure the routes used by the context. Usage of this interface is optional, but + * recommended for consistency. + */ +public interface ConfigurableRoute { + + /** + * Creates the route builder and add it to the context + * @param context the context to configure + * @throws Exception if the context cannot be configured + */ + void createRouteBuilder(CamelContext context) throws Exception; +} diff --git a/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java b/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java index 92c376546dffc..57baa6311c853 100644 --- a/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java +++ b/test-infra/camel-test-infra-kafka/src/test/java/org/apache/camel/test/infra/kafka/services/KafkaService.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Provides an interface for any type of Kafka service: remote instances, local container, etc @@ -39,7 +41,17 @@ public interface KafkaService @Override default void beforeAll(ExtensionContext extensionContext) throws Exception { - initialize(); + try { + initialize(); + } catch (Exception e) { + Logger log = LoggerFactory.getLogger(KafkaService.class); + + final Object o = extensionContext.getTestInstance().get(); + log.error("Failed to initialize service {} for test {} on ({})", this.getClass().getSimpleName(), + extensionContext.getDisplayName(), o.getClass().getName()); + + throw e; + } } @Override diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 84331f994815b..21b6f2ca2c2e6 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -75,5 +75,6 @@ camel-test-infra-hashicorp-vault camel-test-infra-jetty camel-test-infra-etcd3 + camel-test-infra-core