diff --git a/components-starter/camel-platform-http-starter/pom.xml b/components-starter/camel-platform-http-starter/pom.xml index 00194e53b18..7ae909d8e52 100644 --- a/components-starter/camel-platform-http-starter/pom.xml +++ b/components-starter/camel-platform-http-starter/pom.xml @@ -55,6 +55,24 @@ ${rest-assured-version} test + + org.springframework.boot + spring-boot-starter-security + ${spring-boot-version} + test + + + org.springframework.session + spring-session-hazelcast + 3.3.2 + test + + + com.hazelcast + hazelcast + ${hazelcast-version} + test + org.eclipse.jetty jetty-server @@ -65,6 +83,27 @@ org.springframework spring-aop + + org.apache.camel.springboot + camel-jackson-starter + test + + + org.apache.camel.springboot + camel-http-starter + test + + + org.wiremock.integrations + wiremock-spring-boot + 3.0.2 + test + + + org.apache.camel.springboot + camel-jsonpath-starter + test + org.apache.camel.springboot diff --git a/components-starter/camel-platform-http-starter/src/main/docs/platform-http.json b/components-starter/camel-platform-http-starter/src/main/docs/platform-http.json index ac25fa04700..d058867af90 100644 --- a/components-starter/camel-platform-http-starter/src/main/docs/platform-http.json +++ b/components-starter/camel-platform-http-starter/src/main/docs/platform-http.json @@ -17,15 +17,13 @@ "name": "camel.component.platform-http.autowired-enabled", "type": "java.lang.Boolean", "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc.", - "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration", - "defaultValue": true + "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration" }, { "name": "camel.component.platform-http.bridge-error-handler", "type": "java.lang.Boolean", "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored.", - "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration", - "defaultValue": false + "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration" }, { "name": "camel.component.platform-http.customizer.enabled", @@ -48,8 +46,7 @@ "name": "camel.component.platform-http.handle-write-response-error", "type": "java.lang.Boolean", "description": "When Camel is complete processing the message, and the HTTP server is writing response. This option controls whether Camel should catch any failure during writing response and store this on the Exchange, which allows onCompletion\/UnitOfWork to regard the Exchange as failed and have access to the caused exception from the HTTP server.", - "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration", - "defaultValue": false + "sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration" }, { "name": "camel.component.platform-http.header-filter-strategy", diff --git a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/CamelRequestHandlerMapping.java b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/CamelRequestHandlerMapping.java index 91d84e4c4d8..84e05a9b6c1 100644 --- a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/CamelRequestHandlerMapping.java +++ b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/CamelRequestHandlerMapping.java @@ -101,9 +101,19 @@ private RequestMappingInfo asRequestMappingInfo(HttpEndpointModel model) { } } - RequestMappingInfo info = RequestMappingInfo.paths(model.getUri()) - .methods(methods.toArray(new RequestMethod[0])).options(this.getBuilderConfiguration()).build(); - return info; + RequestMappingInfo.Builder info = RequestMappingInfo + .paths(model.getUri()) + .methods(methods.toArray(new RequestMethod[0])) + .options(this.getBuilderConfiguration()); + + if (model.getConsumes() != null) { + info.consumes(model.getConsumes().split(",")); + } + if (model.getProduces() != null) { + info.produces(model.getProduces().split(",")); + } + + return info.build(); } } diff --git a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java index 4e8abf92fe3..9d8e151ea38 100644 --- a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java +++ b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/PlatformHttpMessage.java @@ -20,6 +20,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.camel.Exchange; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.StreamCache; import org.apache.camel.http.common.HttpBinding; import org.apache.camel.support.DefaultMessage; import org.apache.camel.util.ObjectHelper; @@ -60,7 +61,12 @@ public void init(Exchange exchange, HttpBinding binding, HttpServletRequest requ if (flag != null && flag) { this.setHeader("CamelSkipWwwFormUrlEncoding", Boolean.TRUE); } - + // we need to favor using stream cache so the body can be re-read later when routing the message + StreamCache newBody = camelContext.getTypeConverter().tryConvertTo(StreamCache.class, + exchange, getBody()); + if (newBody != null) { + setBody(newBody); + } binding.readRequest(request, this); } diff --git a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpBinding.java b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpBinding.java index 4ff0fee0c58..04a35f36e09 100644 --- a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpBinding.java +++ b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpBinding.java @@ -16,13 +16,35 @@ */ package org.apache.camel.component.platform.http.springboot; +import jakarta.activation.DataHandler; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; -import org.apache.camel.Message; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.camel.*; +import org.apache.camel.attachment.AttachmentMessage; +import org.apache.camel.attachment.CamelFileDataSource; import org.apache.camel.component.platform.http.PlatformHttpEndpoint; +import org.apache.camel.converter.stream.CachedOutputStream; import org.apache.camel.http.base.HttpHelper; import org.apache.camel.http.common.DefaultHttpBinding; +import org.apache.camel.support.ExchangeHelper; +import org.apache.camel.util.FileUtil; +import org.apache.camel.util.IOHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +import java.io.*; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; public class SpringBootPlatformHttpBinding extends DefaultHttpBinding { + private static final Logger LOG = LoggerFactory.getLogger(SpringBootPlatformHttpBinding.class); protected void populateRequestParameters(HttpServletRequest request, Message message) { super.populateRequestParameters(request, message); @@ -47,4 +69,150 @@ private boolean useRestMatching(String path) { return path.indexOf('{') > -1; } + @Override + protected void populateAttachments(HttpServletRequest request, Message message) { + // check if there is multipart files, if so will put it into DataHandler + if (request instanceof MultipartHttpServletRequest multipartHttpServletRequest) { + File tmpFolder = (File) request.getServletContext().getAttribute(ServletContext.TEMPDIR); + multipartHttpServletRequest.getFileMap().forEach((name, multipartFile) -> { + try { + Path uploadedTmpFile = Paths.get(tmpFolder.getPath(), name); + multipartFile.transferTo(uploadedTmpFile); + + if (name != null) { + name = name.replaceAll("[\n\r\t]", "_"); + } + + boolean accepted = true; + + if (getFileNameExtWhitelist() != null) { + String ext = FileUtil.onlyExt(name); + if (ext != null) { + ext = ext.toLowerCase(Locale.US); + if (!getFileNameExtWhitelist().equals("*") && !getFileNameExtWhitelist().contains(ext)) { + accepted = false; + } + } + } + + if (accepted) { + AttachmentMessage am = message.getExchange().getMessage(AttachmentMessage.class); + am.addAttachment(name, new DataHandler(new CamelFileDataSource(uploadedTmpFile.toFile(), name))); + } else { + LOG.debug( + "Cannot add file as attachment: {} because the file is not accepted according to fileNameExtWhitelist: {}", + name, getFileNameExtWhitelist()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } + + public Object parseBody(HttpServletRequest request, Message message) throws IOException { + if (request instanceof StandardMultipartHttpServletRequest) { + return null; + } + + return super.parseBody(request, message); + } + + protected void doWriteDirectResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException { + String contentType = (String)message.getHeader("Content-Type", String.class); + if ("application/x-java-serialized-object".equals(contentType)) { + if (!isAllowJavaSerializedObject() && !this.isTransferException()) { + throw new RuntimeCamelException("Content-type application/x-java-serialized-object is not allowed"); + } else { + try { + Object object = message.getMandatoryBody(Serializable.class); + org.apache.camel.http.common.HttpHelper.writeObjectToServletResponse(response, object); + } catch (InvalidPayloadException var19) { + InvalidPayloadException e = var19; + throw new IOException(e); + } + } + } else { + InputStream is = null; + if (this.checkChunked(message, exchange)) { + is = message.getBody(InputStream.class); + } else if (!this.isText(contentType)) { + is = exchange.getContext().getTypeConverter().tryConvertTo(InputStream.class, message.getBody()); + } + + int len; + if (is != null) { + ServletOutputStream os = response.getOutputStream(); + if (!this.checkChunked(message, exchange)) { + CachedOutputStream stream = new CachedOutputStream(exchange); + + try { + len = this.copyStream(is, stream, response.getBufferSize()); + response.setContentLength(len); + OutputStream current = stream.getCurrentStream(); + if (current instanceof ByteArrayOutputStream) { + ByteArrayOutputStream bos = (ByteArrayOutputStream)current; + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming (direct) response in non-chunked mode with content-length {}", len); + } + + bos.writeTo(os); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming response in non-chunked mode with content-length {} and buffer size: {}", len, len); + } + + this.copyStream(stream.getInputStream(), os, len); + } + } finally { + IOHelper.close(new Closeable[]{is, os}); + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Streaming response in chunked mode with buffer size {}", response.getBufferSize()); + } + + this.copyStream(is, os, response.getBufferSize()); + } + } else { + Object body = message.getBody(); + if (body instanceof String) { + String data = message.getBody(String.class); + + if (data != null) { + String charset = ExchangeHelper.getCharsetName(exchange, true); + len = data.getBytes(charset).length; + response.setCharacterEncoding(charset); + response.setContentLength(len); + if (LOG.isDebugEnabled()) { + LOG.debug("Writing response in non-chunked mode as plain text with content-length {} and buffer size: {}", len, response.getBufferSize()); + } + + try { + response.getWriter().print(data); + } finally { + response.getWriter().flush(); + } + } + } else if (body instanceof InputStream) { + InputStream bodyIS = message.getBody(InputStream.class); + bodyIS.transferTo(response.getOutputStream()); + } else { + final TypeConverter tc = exchange.getContext().getTypeConverter(); + // Try to convert to ByteBuffer for performance reason + final ByteBuffer bb = tc.tryConvertTo(ByteBuffer.class, exchange, body); + if (bb != null) { + response.getOutputStream().write(bb.array()); + } else { + try { + final InputStream bodyIS = tc.mandatoryConvertTo(InputStream.class, exchange, body); + bodyIS.transferTo(response.getOutputStream()); + } catch (NoTypeConversionAvailableException e) { + throw new RuntimeException(e); + } + } + } + } + } + } } diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java index 302149febbf..1dcc7e50dd1 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpAsyncRequestHandlingTest.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; @@ -32,14 +34,11 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java index 79d5cb321d2..1e4fa6e7373 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpBase.java @@ -16,7 +16,6 @@ */ package org.apache.camel.component.platform.http.springboot; -import java.util.concurrent.TimeUnit; import org.apache.camel.CamelContext; import org.apache.camel.ServiceStatus; import org.assertj.core.api.Assertions; @@ -25,6 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; +import java.util.concurrent.TimeUnit; + import static org.junit.jupiter.api.Assertions.assertEquals; abstract class PlatformHttpBase { diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpStreamingTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpStreamingTest.java index 05962f12349..86f3c056a90 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpStreamingTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/PlatformHttpStreamingTest.java @@ -24,7 +24,9 @@ import org.apache.camel.util.IOHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; @@ -42,7 +44,7 @@ import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; -@SpringBootApplication +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelIntegrationsTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelIntegrationsTest.java new file mode 100644 index 00000000000..b3bd564cff7 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelIntegrationsTest.java @@ -0,0 +1,89 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static io.restassured.RestAssured.given; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpCamelIntegrationsTest.class, SpringBootPlatformHttpCamelIntegrationsTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpCamelIntegrationsTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + rest().post("message").to("direct:route"); + from("direct:route") + .choice() + .when().jsonpath("$[?(@.counter>0)]") + .setBody(constant("positive")) + .otherwise() + .setBody(constant("negative")) + .end(); + } + }; + } + } + + @Test + public void test() { + String data = "{\"counter\":1}"; + given() + .body(data) + .header("Content-Type", "application/json") + .when() + .post("/message") + .then() + .statusCode(200) + .body(Matchers.is("positive")); + + data = "{\"counter\":0}"; + given() + .body(data) + .header("Content-Type", "application/json") + .when() + .post("/message") + .then() + .statusCode(200) + .body(Matchers.is("negative")); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCertificationTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCertificationTest.java new file mode 100644 index 00000000000..a889cf1a7a0 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCertificationTest.java @@ -0,0 +1,360 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.platform.http.cookie.CookieConfiguration; +import org.apache.camel.component.platform.http.cookie.CookieHandler; +import org.apache.camel.model.rest.RestBindingMode; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.apache.camel.util.IOHelper; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Random; + +import static io.restassured.RestAssured.given; +import static io.restassured.matcher.RestAssuredMatchers.detailedCookie; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpCertificationTest.class, SpringBootPlatformHttpCertificationTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpCertificationTest extends PlatformHttpBase { + + private static final String postRouteId = "SpringBootPlatformHttpRestDSLTest_mypost"; + private static final String getRouteId = "SpringBootPlatformHttpRestDSLTest_myget"; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + getCamelContext().getStreamCachingStrategy().setSpoolEnabled(true); + rest() + .get("myget").id(getRouteId).to("direct:get") + .post("mypost").id(postRouteId).to("direct:post"); + + from("direct:post").transform().body(String.class, b -> b.toUpperCase()); + from("direct:get").setBody().constant("get") + .log("hello"); + + rest("rest").get("/test") + .consumes("application/json,application/xml") + .produces("application/json,application/xml") + .bindingMode(RestBindingMode.auto) + .to("direct:rest"); + + from("direct:rest") + .setBody(simple("Hello")); + + from("platform-http:/streaming?useStreaming=true") + .log("Done echoing back request body as response body"); + + from("platform-http:/helloStreaming?useStreaming=true") + .transform().simple("Hello ${body}"); + + from("platform-http:/streamingFileRequestResponseBody?useStreaming=true") + .log("Done processing request"); + + from("platform-http:/streamingWithFormUrlEncodedBody?useStreaming=true") + .setBody().simple("foo = ${header.foo}"); + + from("platform-http:/streamingWithSpecificEncoding?useStreaming=true") + .log("Done echoing back request body as response body"); + + from("platform-http:/streamingWithClosedInputStreamResponse?useStreaming=true") + .process(new Processor() { + @Override + public void process(Exchange exchange) throws Exception { + // Simulate an error processing an input stream by closing it ahead of the response being written + // Verifies the response promise.fail is called correctly + InputStream stream = getClass().getResourceAsStream("/application.properties"); + if (stream != null) { + stream.close(); + } + exchange.getMessage().setBody(stream); + } + }); + + from("platform-http:/streamingWithUnconvertableResponseType?useStreaming=true") + .process(new Processor() { + @Override + public void process(Exchange exchange) { + // Force a type conversion exception and verify the response promise.fail is called correctly + exchange.getMessage().setBody(new TestBean()); + } + }) + .log("Conversion done"); + + from("platform-http:/session") + .setBody().constant("session"); + + from("platform-http:/streamingWithLargeRequestAndResponseBody?useStreaming=true") + .log("Done echoing back request body as response body"); + + from("platform-http:/addCookie?useCookieHandler=true") + .process(exchange -> { + exchange.getProperty(Exchange.COOKIE_HANDLER, CookieHandler.class).addCookie("foo", "bar"); + }) + .setBody().constant("add"); + } + }; + } + } + + @Override + protected String getPostRouteId() { + return postRouteId; + } + + @Override + protected String getGetRouteId() { + return getRouteId; + } + + @Test + public void testLoad() throws Exception { + waitUntilRouteIsStarted(1, getGetRouteId()); + + for (int i = 0; i < 1_000; i++) { + Assertions.assertThat(restTemplate.getForEntity("/myget", String.class).getStatusCode().value()).isEqualTo(200); + } + } + + @Test + public void nonSupportedContentType() { + RestAssured.given() + .header("Content-Type", "notSupported") + .get("rest/test") + .then() + .statusCode(415); + } + + @Test + public void oneContentType() { + RestAssured.given() + .header("Content-type", ContentType.XML) + .header("Accept", ContentType.XML) + .get("rest/test") + .then() + .statusCode(200) + .body(is("Hello")); + } + + @Test + public void nonSupportedAccept() { + RestAssured.given() + .header("Content-type", ContentType.XML) + .header("Accept", ContentType.BINARY) + .get("rest/test") + .then() + .statusCode(406); + } + + @Test + public void streaming() { + String requestBody = "Vert.x Platform HTTP"; + given() + .body(requestBody) + .post("/helloStreaming") + .then() + .statusCode(200) + .body(is("Hello " + requestBody)); + } + + @Test + void streamingFileRequestResponseBody() throws Exception { + String content = "Hello World"; + Path testFile = Files.createTempFile("platform-http-testing", "txt"); + Files.writeString(testFile, content); + + given() + .body(testFile.toFile()) + .post("/streamingFileRequestResponseBody") + .then() + .statusCode(200) + .body(is(content)); + } + + @Test + void streamingWithFormUrlEncodedBody() throws Exception { + given() + .contentType(ContentType.URLENC) + .formParam("foo", "bar") + .post("/streamingWithFormUrlEncodedBody") + .then() + .statusCode(200) + .body(is("foo = bar")); + } + + @Test + void streamingWithSpecificEncoding() throws Exception { + Path input = Files.createTempFile("platform-http-input", "dat"); + Path output = Files.createTempFile("platform-http-output", "dat"); + + String fileContent = "Content with special character ð"; + Files.writeString(input, fileContent, StandardCharsets.ISO_8859_1); + + InputStream response = given() + .body(new FileInputStream(input.toFile())) + .post("/streamingWithSpecificEncoding") + .then() + .statusCode(200) + .extract() + .body() + .asInputStream(); + + try (FileOutputStream fos = new FileOutputStream(output.toFile())) { + IOHelper.copy(response, fos); + } + + assertEquals(fileContent, Files.readString(output, StandardCharsets.ISO_8859_1)); + } + + @Test + @Disabled("Test is failing, work in progress") + void streamingWithClosedInputStreamResponse() throws Exception { + + given() + .get("/streamingWithClosedInputStreamResponse") + .then() + .statusCode(500); + } + + @Test + void streamingWithUnconvertableResponseType() throws Exception { + given() + .get("/streamingWithUnconvertableResponseType") + .then() + .statusCode(500); + } + + static final class TestBean { + } + + @Test + public void session() { + Map cookies = given() + .when() + .get("/session") + .then() + .statusCode(200) +// .cookie("vertx-web.session", +// detailedCookie() +// .path("/").value(notNullValue()) +// .httpOnly(false) +// .secured(false) +// .sameSite("Strict")) +// .header("cookie", nullValue()) + .body(equalTo("session")) + .extract().cookies(); + + System.out.println(cookies); + } + + @Autowired + CamelContext camelContext; + + @Test + @DisabledIfSystemProperty(named = "ci.env.name", matches = "apache.org", + disabledReason = "File too large for Apache CI") + void streamingWithLargeRequestAndResponseBody() throws Exception { + camelContext.getStreamCachingStrategy().setSpoolEnabled(true); + + Path input = createLargeFile(); + Path output = Files.createTempFile("platform-http-output", "dat"); + + InputStream response = given() + .body(new FileInputStream(input.toFile())) + .post("/streaming") + .then() + .extract() + .asInputStream(); + + try (FileOutputStream fos = new FileOutputStream(output.toFile())) { + IOHelper.copy(response, fos); + } + + assertEquals(input.toFile().length(), output.toFile().length()); + } + + private Path createLargeFile() throws IOException { + // Create a 4GB file containing random data + Path path = Files.createTempFile("platform-http-input", "dat"); + try (FileOutputStream fos = new FileOutputStream(path.toFile())) { + Random random = new Random(); + long targetFileSize = (long) (4 * Math.pow(1024, 3)); + long bytesWritten = 0L; + + byte[] data = new byte[1024]; + while (bytesWritten < targetFileSize) { + random.nextBytes(data); + fos.write(data); + bytesWritten += data.length; + } + } + return path; + } + + private static CookieHandler getCookieHandler(Exchange exchange) { + return exchange.getProperty(Exchange.COOKIE_HANDLER, CookieHandler.class); + } + + @Test + @Disabled("Test is failing, work in progress") + public void testAddCookie() throws Exception { + given() + .when() + .get("/addCookie") + .then() + .statusCode(200) + .cookie("foo", + detailedCookie() + .value("bar") + .path(CookieConfiguration.DEFAULT_PATH) + .domain((String) null) + .sameSite(CookieConfiguration.DEFAULT_SAME_SITE.getValue())) + .body(equalTo("add")); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java new file mode 100644 index 00000000000..728aa382dc4 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCookiesTest.java @@ -0,0 +1,144 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import jakarta.servlet.http.Cookie; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.util.Arrays; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpCookiesTest.class, SpringBootPlatformHttpCookiesTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpCookiesTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("platform-http:/add") + .process(exchange -> { + PlatformHttpMessage message = (PlatformHttpMessage) exchange.getMessage(); + message.getResponse().addCookie(new Cookie("foo", "bar")); + }) + .setBody().constant("add"); + + from("platform-http:/remove") + .process(exchange -> { + PlatformHttpMessage message = (PlatformHttpMessage) exchange.getMessage(); + + Cookie cookie = Arrays.stream(message.getRequest().getCookies()).findFirst().get(); + cookie.setMaxAge(0); + cookie.setValue(null); + + message.getResponse().addCookie(cookie); + }) + .setBody().constant("remove"); + + from("platform-http:/replace") + .process(exchange -> { + PlatformHttpMessage message = (PlatformHttpMessage) exchange.getMessage(); + assertEquals(1, message.getRequest().getCookies().length); + + Cookie cookie = new Cookie("XSRF-TOKEN", "88533580000c314"); + cookie.setPath("/"); + message.getResponse().addCookie(cookie); + }) + .setBody().constant("replace"); + + from("platform-http:/echo") + .setBody().constant("echo"); + } + }; + } + } + + @Test + public void addCookie() { + given() + .header("cookie", "foo=bar") + .when() + .get("/add") + .then() + .statusCode(200) + .header("set-cookie", "foo=bar") + .body(equalTo("add")); + } + + @Test + public void removeCookie() { + given() + .header("cookie", "foo=bar") + .when() + .get("/remove") + .then() + .statusCode(200) + .header("set-cookie", startsWith("foo=; Max-Age=0; Expires=")) + .body(equalTo("remove")); + } + + @Test + public void replaceCookie() { + given() + .header("cookie", "XSRF-TOKEN=c359b44aef83415") + .when() + .get("/replace") + .then() + .statusCode(200) + .header("set-cookie", "XSRF-TOKEN=88533580000c314; Path=/") + .body(equalTo("replace")); + } + + @Test + public void echoCookie() { + given() + .header("cookie", "echo=cookie") + .when() + .get("/echo") + .then() + .statusCode(200) + .header("cookie", startsWith("echo=cookie")) + .body(equalTo("echo")); + } + +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCorsTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCorsTest.java new file mode 100644 index 00000000000..c27ba615f46 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCorsTest.java @@ -0,0 +1,118 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static io.restassured.RestAssured.given; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpCorsTest.class, SpringBootPlatformHttpCorsTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpCorsTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + +// @Bean +// public WebMvcConfigurer corsConfigurer() { +// return new WebMvcConfigurer() { +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// registry.addMapping("/**"); +// } +// }; +// } + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + restConfiguration().component("platform-http").enableCORS(true); + + rest("/rest") + .post() + .consumes("application/json") + .to("direct:rest"); + + from("direct:rest") + .setBody(simple("Hello ${body}")); + + from("platform-http:/cors") + .transform().constant("cors"); + } + }; + } + } + + @Test + @Disabled("Test is failing, work in progress") + public void cors() { + final String origin = "http://custom.origin.springboot"; + final String methods = "GET,POST"; + final String headers = "X-Custom"; + + given() + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Headers", headers) + .when() + .get("/cors") + .then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Headers", headers); + } + + @Test + @Disabled("Test is failing, work in progress") + public void corsWithConsumes() { + final String origin = "http://custom.origin.springboot"; + + given() + .header("Origin", origin) + .when() + .options("/rest") + .then() + .statusCode(204) + .header("Access-Control-Allow-Origin", RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN) + .header("Access-Control-Allow-Methods", RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS) + .header("Access-Control-Allow-Headers", RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS) + .header("Access-Control-Max-Age", RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpEngineTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpEngineTest.java new file mode 100644 index 00000000000..eff6fe0c3e5 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpEngineTest.java @@ -0,0 +1,237 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import jakarta.activation.DataHandler; +import org.apache.camel.CamelContext; +import org.apache.camel.attachment.AttachmentMessage; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collections; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpEngineTest.class, SpringBootPlatformHttpEngineTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpEngineTest { + private final static String attachmentId = "myTestFile"; + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + rest() + .post("upload").to("direct:upload"); + + from("direct:upload") + .process(exchange -> { + AttachmentMessage message = exchange.getMessage(AttachmentMessage.class); + DataHandler attachment = message.getAttachment(attachmentId); + message.setBody(attachment.getContent()); + }); + + from("platform-http:/form/post") + .convertBodyTo(String.class); + + from("platform-http:/greeting/{name}?matchOnUriPrefix=true") + .transform().simple("Hello ${header.name}"); + + from("platform-http:/text/post") + .log("POST:/test/post has body ${body}"); + + from("platform-http:/responseHeaders") + .setHeader("nonEmptyFromRoute", constant("nonEmptyFromRouteValue")) + .setHeader("emptyFromRoute", constant("")) + .setBody().simple("Hello World"); + + from("platform-http:/multipleHeaders") + .setHeader("nonEmptyFromRoute", constant("nonEmptyFromRouteValue")) + .setBody().simple("Hello World"); + + from("platform-http:/consumerSuspended") + .routeId("consumerSuspended") + .setBody().constant("get"); + + from("platform-http:/error/response") + // Set the response to something that can't be type converted + .setBody().constant(Collections.EMPTY_SET); + + from("platform-http:/error/query/param") + .setBody().constant("Error"); + } + }; + } + } + + @Test + public void testAttachment() throws Exception { + final String attachmentId = "myTestFile"; + final String fileContent = "Test multipart upload content"; + final File tempFile = File.createTempFile("platform-http", ".txt"); + + Files.write(tempFile.toPath(), fileContent.getBytes(StandardCharsets.UTF_8)); + + given() + .multiPart(attachmentId, tempFile) + .when() + .post("/upload") + .then() + .statusCode(200) + .body(is(fileContent)); + } + + @Test + public void formPost() { + given() + .formParam("foo", "bar") + .formParam("cheese", "wine") + .when() + .post("/form/post") + .then() + .statusCode(200) + .body(is("foo=bar&cheese=wine")); + } + + @Test + @Disabled("Test is failing, work in progress") + public void matchOnUriPrefix() { + final String greeting = "Hello Camel"; + given() + .when() + .get("/greeting") + .then() + .statusCode(404); + + given() + .when() + .get("/greeting/Camel") + .then() + .statusCode(200) + .body(equalTo(greeting)); + + given() + .when() + .get("/greeting/Camel/other/path/") + .then() + .statusCode(200) + .body(equalTo(greeting)); + } + + @Test + public void textContentPost() { + String payload = "Hello World"; + given() + .contentType(ContentType.TEXT) + .body(payload) + .when() + .post("/text/post") + .then() + .statusCode(200) + .body(is(payload)); + } + + @Test + public void responseHeaders() { + RestAssured.given() + .header("nonEmpty", "nonEmptyValue") + .header("empty", "") + .get("/responseHeaders") + .then() + .statusCode(200) + .body(equalTo("Hello World")) + .header("nonEmpty", "nonEmptyValue") + .header("empty", "") + .header("nonEmptyFromRoute", "nonEmptyFromRouteValue") + .header("emptyFromRoute", ""); + } + + @Test + public void multipleHeaders() { + RestAssured.given() + .header("nonEmpty", "nonEmptyValue") + .header("empty", "") + .get("/multipleHeaders?duplicated=1&duplicated=2") + .then() + .statusCode(200) + .body(equalTo("Hello World")) + .header("nonEmpty", "nonEmptyValue") + .header("nonEmptyFromRoute", "nonEmptyFromRouteValue"); + } + + @Test + @Disabled("Test is failing, work in progress") + public void consumerSuspended() throws Exception { + given() + .when() + .get("/consumerSuspended") + .then() + .statusCode(200) + .body(equalTo("get")); + + camelContext.getRouteController().suspendRoute("consumerSuspended"); + + given() + .when() + .get("/get") + .then() + .statusCode(503); + } + + @Test + public void responseTypeConversionErrorHandled() { + get("/error/response") + .then() + .statusCode(500); + } + + @Test + @Disabled("Test is failing, work in progress") + public void responseBadQueryParamErrorHandled() { + get("/error/query/param?::") + .then() + .statusCode(500); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java index 37a3cb384bb..4be423aab44 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpHandleWriteErrorTest.java @@ -30,6 +30,8 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,7 +41,7 @@ import java.io.IOException; -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpJacksonConverterTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpJacksonConverterTest.java new file mode 100644 index 00000000000..cd8db321d44 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpJacksonConverterTest.java @@ -0,0 +1,74 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jackson.JacksonConstants; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpJacksonConverterTest.class, SpringBootPlatformHttpJacksonConverterTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpJacksonConverterTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + getContext().getGlobalOptions().put(JacksonConstants.ENABLE_TYPE_CONVERTER, "true"); + + from("platform-http:/hello") + .setBody().constant("{\"hello\": \"world\"}") + .unmarshal().json(); + } + }; + } + } + + @Test + public void jacksonTypeConverter() { + given() + .when() + .get("/hello") + .then() + .statusCode(200) + .body(equalTo("{\"hello\":\"world\"}")); + } +} \ No newline at end of file diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpMultipleExecutorsTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpMultipleExecutorsTest.java index 003afbae899..8361c67b9ba 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpMultipleExecutorsTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpMultipleExecutorsTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,7 +19,7 @@ import java.util.List; import java.util.concurrent.Executor; -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpProxyTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpProxyTest.java new file mode 100644 index 00000000000..ea6dc19ca0c --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpProxyTest.java @@ -0,0 +1,93 @@ +package org.apache.camel.component.platform.http.springboot; + +import com.github.tomakehurst.wiremock.client.WireMock; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpProxyTest.class, SpringBootPlatformHttpProxyTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +@EnableWireMock(@ConfigureWireMock(portProperties = "customPort")) +public class SpringBootPlatformHttpProxyTest { + + @Value("${wiremock.server.baseUrl}") + private String wiremockUrl; + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + WireMock.stubFor(get(urlPathEqualTo("/")) + .willReturn(aResponse() + .withBody( + "{\"message\": \"Hello World\"}"))); + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("platform-http:proxy?useStreaming=false") + .log("${headers}") + .toD("${headers." + Exchange.HTTP_URI + "}?bridgeEndpoint=true"); + } + }; + } + } + + @Test + @Disabled("Test is failing, work in progress") + public void httpProxy() { + final var proxyURI = "http://localhost:" + RestAssured.port; + + final var originURI = wiremockUrl; + + given() + .proxy(proxyURI) + .contentType(ContentType.JSON) + .when().get(originURI) + .then() + .statusCode(200) + .body(containsString("{\"message\": \"Hello World\"}")); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java index b6761b9877d..c9e4b5b17f6 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpRestDSLTest.java @@ -20,13 +20,15 @@ import org.apache.camel.spring.boot.CamelAutoConfiguration; import org.apache.camel.test.spring.junit5.CamelSpringBootTest; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSessionTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSessionTest.java new file mode 100644 index 00000000000..3a44c03e6b1 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSessionTest.java @@ -0,0 +1,158 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import io.restassured.http.Cookie; +import io.restassured.internal.ValidatableResponseImpl; +import io.restassured.response.Response; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.annotation.DirtiesContext; + +import static io.restassured.RestAssured.given; + +@EnableAutoConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpSessionTest.class, SpringBootPlatformHttpSessionTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpSessionTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + @EnableWebSecurity + public static class TestConfiguration { + + @Bean + public UserDetailsService userDetailsService() { + UserDetails user = User.builder() + .username("user") + .password("{noop}password") + .authorities("USER") + .build(); + + UserDetails admin = User.builder() + .username("admin") + .password("{noop}password") + .authorities("ADMIN") + .build(); + + return new InMemoryUserDetailsManager(user, admin); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable()) + .authorizeHttpRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()) + .build(); + } + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + rest("/authenticated") + .post("/first") + .to("direct:first") + .get("/second") + .to("direct:second"); + + from("direct:first") + .process(exchange -> { + PlatformHttpMessage message = (PlatformHttpMessage) exchange.getMessage(); + message.getRequest().getSession() + .setAttribute("json-attribute", exchange.getMessage().getBody(String.class)); + }); + + from("direct:second") + .process(exchange -> { + PlatformHttpMessage message = (PlatformHttpMessage) exchange.getMessage(); + + exchange.getIn().setBody( + message.getRequest().getSession().getAttribute("json-attribute")); + }); + } + }; + } + } + + protected String getSessionKey() { + return "JSESSIONID"; + } + + @Test + public void session() { + String jsonAttribute = """ + {"test":"attribute"} + """; + + var result = given() + .auth().basic("user", "password") + .body(jsonAttribute) + .when() + .post("/authenticated/first") + .then() + .statusCode(200) + .cookie(getSessionKey()); + + String session = ((ValidatableResponseImpl) result).originalResponse().cookies().get(getSessionKey()); + + result = given() + .auth().basic("user", "password") + .cookie(new Cookie.Builder(getSessionKey(), session).build()) + .when() + .get("/authenticated/second") + .then() + .statusCode(200) + .body(Matchers.is(jsonAttribute)); + + Response response = ((ValidatableResponseImpl) result).originalResponse(); + + result = given() + .auth().basic("admin", "password") + .when() + .get("/authenticated/second") + .then() + .statusCode(204) + .body(Matchers.is("")); + + response = ((ValidatableResponseImpl) result).originalResponse(); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSpringSessionTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSpringSessionTest.java new file mode 100644 index 00000000000..d8559bdba37 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpSpringSessionTest.java @@ -0,0 +1,51 @@ +package org.apache.camel.component.platform.http.springboot; + +import com.hazelcast.config.*; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; +import org.springframework.session.hazelcast.PrincipalNameExtractor; +import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; +import org.springframework.test.annotation.DirtiesContext; + +@EnableAutoConfiguration +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpSpringSessionTest.class, SpringBootPlatformHttpSpringSessionTest.TestConfiguration.class, + SpringBootPlatformHttpSessionTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpSpringSessionTest extends SpringBootPlatformHttpSessionTest { + + @Override + protected String getSessionKey() { + return "SESSION"; + } + + @Configuration + @EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = 150) + public static class TestConfiguration { + + @Bean(destroyMethod = "shutdown") + public HazelcastInstance hazelcastInstance() { + Config config = new Config(); + NetworkConfig networkConfig = config.getNetworkConfig(); + networkConfig.setPort(0); + networkConfig.getJoin().getAutoDetectionConfig().setEnabled(false); + AttributeConfig attributeConfig = new AttributeConfig() + .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setExtractorClassName(PrincipalNameExtractor.class.getName()); + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) + .addAttributeConfig(attributeConfig) + .addIndexConfig( + new IndexConfig(IndexType.HASH, HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)); + return Hazelcast.newHazelcastInstance(config); + } + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java index 77355b21277..a844a15f2a8 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTest.java @@ -20,13 +20,15 @@ import org.apache.camel.spring.boot.CamelAutoConfiguration; import org.apache.camel.test.spring.junit5.CamelSpringBootTest; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) @CamelSpringBootTest @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTypeConverterTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTypeConverterTest.java new file mode 100644 index 00000000000..242ec634015 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpTypeConverterTest.java @@ -0,0 +1,119 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import org.apache.camel.*; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpTypeConverterTest.class, SpringBootPlatformHttpTypeConverterTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpTypeConverterTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + getContext().getTypeConverterRegistry().addTypeConverter(InputStream.class, + Map.class, + new InputStreamTypeConverter()); + + from("platform-http:/inputstream") + .routeId("inputstream") + .setBody().constant(Collections.singletonMap("is", "my-test")); + } + }; + } + } + + @Test + public void customTypeConverter() { + given() + .when() + .get("/inputstream") + .then() + .statusCode(200) + .body(equalTo("InputStream:my-test")); + } + + static class InputStreamTypeConverter implements TypeConverter { + @Override + public boolean allowNull() { + return false; + } + + @Override + public T convertTo(Class type, Object value) throws TypeConversionException { + return null; + } + + @Override + public T convertTo(Class type, Exchange exchange, Object value) throws TypeConversionException { + byte[] out = ("InputStream:" + ((Map) value).get("is")).getBytes(StandardCharsets.UTF_8); + return type.cast(new ByteArrayInputStream(out)); + } + + @Override + public T mandatoryConvertTo(Class type, Object value) throws TypeConversionException, NoTypeConversionAvailableException { + return null; + } + + @Override + public T mandatoryConvertTo(Class type, Exchange exchange, Object value) throws TypeConversionException, NoTypeConversionAvailableException { + return null; + } + + @Override + public T tryConvertTo(Class type, Object value) { + return null; + } + + @Override + public T tryConvertTo(Class type, Exchange exchange, Object value) { + return null; + } + } +} \ No newline at end of file diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpValidationTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpValidationTest.java new file mode 100644 index 00000000000..57439d40a65 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpValidationTest.java @@ -0,0 +1,175 @@ +package org.apache.camel.component.platform.http.springboot; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.response.ValidatableResponse; +import io.restassured.specification.RequestSpecification; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.platform.http.spi.Method; +import org.apache.camel.model.rest.RestBindingMode; +import org.apache.camel.model.rest.RestParamType; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit5.CamelSpringBootTest; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; + +import java.util.List; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpValidationTest.class, SpringBootPlatformHttpValidationTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, }) +public class SpringBootPlatformHttpValidationTest { + + @Autowired + TestRestTemplate restTemplate; + + @Autowired + CamelContext camelContext; + + @LocalServerPort + private Integer port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Configuration + public static class TestConfiguration { + + @Bean + public RouteBuilder springBootPlatformHttpRestDSLRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + rest("/rest") + .post("/validate/body") + .clientRequestValidation(true) + .param().name("body").type(RestParamType.body).required(true).endParam() + .to("direct:rest"); + from("direct:rest") + .setBody(simple("Hello ${body}")); + + from("platform-http:/echo") + .setBody().simple("${body}"); + + from("platform-http:/test") + .setBody().simple("Hello ${body[method]}"); + + rest("/invalidContentTypeClientRequestValidation") + .clientRequestValidation(true) + .bindingMode(RestBindingMode.json) + .post("/validate/body") + .consumes("text/plain") + .produces("application/json") + .to("direct:invalidContentTypeClientRequestValidation"); + from("direct:invalidContentTypeClientRequestValidation") + .setBody(simple("Hello ${body}")); + } + }; + } + } + + @Test + public void requestValidation() throws Exception { + given() + .when() + .post("/rest/validate/body") + .then() + .statusCode(400) + .body(is("The request body is missing.")); + + given() + .body(" ") + .when() + .post("/rest/validate/body") + .then() + .statusCode(400) + .body(is("The request body is missing.")); + + given() + .body("Camel Platform HTTP Vert.x") + .when() + .post("/rest/validate/body") + .then() + .statusCode(200) + .body(is("Hello Camel Platform HTTP Vert.x")); + } + + @Test + @Disabled("Test is failing, work in progress") + public void requestBodyAllowed() { + for (Method method : Method.values()) { + ValidatableResponse validatableResponse = given() + .contentType(ContentType.JSON) + .when() + .body("{\"method\": \"" + method + "\"}") + .request(method.name(), "/echo") + .then() + .statusCode(200); + + Matcher expectedBody; + if (method.equals(Method.HEAD)) { + // HEAD response body is ignored + validatableResponse.body(emptyString()); + } else { + validatableResponse.body("method", equalTo(method.name())); + } + } + } + + @Test + @Disabled("Test is failing, work in progress") + public void requestBodyAllowedFormUrlEncoded() { + final List methodsWithBodyAllowed = List.of(Method.POST, Method.PUT, Method.PATCH, Method.DELETE); + + RequestSpecification request = given() + .when() + .contentType(ContentType.URLENC); + + for (Method method : Method.values()) { + if (methodsWithBodyAllowed.contains(method)) { + request.body("method=" + method) + .request(method.name(), "/test") + .then() + .statusCode(200) + .body(equalTo("Hello " + method)); + } else { + request.body(method) + .request(method.name(), "/test") + .then() + .statusCode(500); + } + } + } + + @Test + public void invalidContentTypeClientRequestValidation() { + given() + .when() + .body("{\"name\": \"Donald\"}") + .contentType("application/json") + .post("/invalidContentTypeClientRequestValidation/validate/body") + .then() + .statusCode(415); + } +}