diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/pom.xml b/spring/fluentforms-jersey-spring-boot-autoconfigure/pom.xml index c8f104cc..ae5b642c 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/pom.xml +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/pom.xml @@ -3,7 +3,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 com._4point.aem.fluentforms @@ -20,7 +20,8 @@ 0.0.4-SNAPSHOT - 4.0.0-beta.16 + 4.0.0-beta.22 + 4.0.8 1.20.2 1.2.3 @@ -94,7 +95,7 @@ org.springframework.boot - spring-boot-starter-test + spring-boot-starter-jersey-test test @@ -105,10 +106,22 @@ org.wiremock - wiremock-standalone + wiremock ${wiremock.version} test + + org.wiremock + wiremock-junit5 + ${wiremock.version} + test + + + org.wiremock.integrations + wiremock-spring-boot + ${wiremock-spring-boot.version} + test + org.pitest pitest-junit5-plugin diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfiguration.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfiguration.java index c6070e67..3847199c 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfiguration.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfiguration.java @@ -12,8 +12,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.system.JavaVersion; import org.springframework.context.annotation.Bean; diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpoint.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpoint.java index 84ef323e..001ceabf 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpoint.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpoint.java @@ -204,7 +204,9 @@ public Response proxyPost(@PathParam("remainder") String remainder, @HeaderParam .log("Returning POST response from target '{}'."); } - return Response.fromResponse(result).build(); + return Response.fromResponse(result) + .header("Transfer-Encoding", null) // Remove the Transfer-Encoding header + .build(); } private InputStream debugInput(InputStream in, String target) { diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfigurationTest.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfigurationTest.java index cfb13982..a12bec70 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfigurationTest.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyAutoConfigurationTest.java @@ -6,11 +6,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jersey.autoconfigure.ResourceConfigCustomizer; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest(classes = {com._4point.aem.fluentforms.spring.FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class}, +@SpringBootTest(classes = {com._4point.aem.fluentforms.spring.FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, properties = { "fluentforms.aem.servername=localhost", "fluentforms.aem.port=4502", diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpointTest.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpointTest.java index ae37cd30..6e98b99f 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpointTest.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyJerseyEndpointTest.java @@ -23,16 +23,17 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.web.client.RestClient; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; -@WireMockTest(httpPort = AemProxyJerseyEndpointTest.WIREMOCK_PORT) +@WireMockTest() @SpringBootTest(classes = {com._4point.aem.fluentforms.spring.AemProxyJerseyEndpointTest.TestApplication.class}, webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "fluentforms.aem.servername=localhost", -"fluentforms.aem.port=" + AemProxyJerseyEndpointTest.WIREMOCK_PORT, "fluentforms.aem.user=ENC(7FgD3ZsSExfUGRYlXNc++6C1upPBURNKq6HouzagnNZW4FsBwFs5+crawv+djhw6)", "fluentforms.aem.password=ENC(QmQ6iTm/+TOO8U3dDuBzJWH129vReWgYNdgqQwWhjWaQy6j8sMnk2/Auhehmlh3v)", //"fluentforms.aem.useSsl=true", @@ -41,13 +42,18 @@ "jasypt.encryptor.password=4Point", "jasypt.encryptor.iv-generator-classname=org.jasypt.iv.RandomIvGenerator", "jasypt.encryptor.salt-generator-classname=org.jasypt.salt.RandomSaltGenerator", -"logging.level.com._4point.aem.fluentforms.spring.AemProxyEndpoint=DEBUG" +"logging.level.com._4point.aem.fluentforms.spring.AemProxyEndpoint=DEBUG", +// Wiremock produces a lot of output, the following entries reduce that output. They can be removed for debugging. +"logging.level.org.wiremock.spring=WARN", "logging.level.WireMock.wiremock=WARN", }) -@Timeout(value = 5, unit = TimeUnit.MINUTES) // Fail tests that take longer than this to prevent hanging. +@EnableWireMock(@ConfigureWireMock( + portProperties = "fluentforms.aem.port" + ) + ) +@Timeout(value = 30, unit = TimeUnit.SECONDS) // Fail tests that take longer than this to prevent hanging. class AemProxyJerseyEndpointTest { private final static Logger logger = LoggerFactory.getLogger(AemProxyJerseyEndpointTest.class); - static final int WIREMOCK_PORT = 5504; static final String AF_BASE_LOCATION = "/aem"; // The following is a string that contains all possible values that may be modified by the AemProxyEndpoint. diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/FluentFormsJerseyAutoConfigurationTest.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/FluentFormsJerseyAutoConfigurationTest.java index 369a2f05..21b929e0 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/FluentFormsJerseyAutoConfigurationTest.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/FluentFormsJerseyAutoConfigurationTest.java @@ -32,7 +32,6 @@ import com._4point.aem.fluentforms.api.generatePDF.GeneratePDFService; import com._4point.aem.fluentforms.api.output.OutputService; import com._4point.aem.fluentforms.api.pdfUtility.PdfUtilityService; -import com._4point.aem.fluentforms.spring.rest_services.client.SpringRestClientRestClient; @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class}, properties = { @@ -158,40 +157,42 @@ private static String inputStreamToString(InputStream inputStream) throws IOExce return result; } - @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, - properties = { - "fluentforms.aem.servername=localhost", - "fluentforms.aem.port=4502", - "fluentforms.aem.user=admin", - "fluentforms.aem.password=admin", - "fluentforms.restclient=springrestclient" // Configure for Spring RestClient - }) - public static class SpringRestClientTest { - - @Test - void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { - RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); - assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); - } - } +// TODO: For now we are going to ignore mixed scenarios (where we are using one starter with a different rest client +// @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, +// properties = { +// "fluentforms.aem.servername=localhost", +// "fluentforms.aem.port=4502", +// "fluentforms.aem.user=admin", +// "fluentforms.aem.password=admin", +// "fluentforms.restclient=springrestclient" // Configure for Spring RestClient +// }) +// public static class SpringRestClientTest { +// +// @Test +// void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { +// RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); +// assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); +// } +// } - @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, - properties = { - "fluentforms.aem.servername=localhost", - "fluentforms.aem.port=4502", - "fluentforms.aem.user=admin", - "fluentforms.aem.password=admin", - "fluentforms.aem.usessl=true", - "fluentforms.restclient=springrestclient" // Configure for Spring RestClient - }) - public static class SpringRestClient_SslNoBundleNameTest { - - @Test - void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { - RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); - assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); - } - } +// TODO: For now we are going to ignore mixed scenarios (where we are using one starter with a different rest client +// @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, +// properties = { +// "fluentforms.aem.servername=localhost", +// "fluentforms.aem.port=4502", +// "fluentforms.aem.user=admin", +// "fluentforms.aem.password=admin", +// "fluentforms.aem.usessl=true", +// "fluentforms.restclient=springrestclient" // Configure for Spring RestClient +// }) +// public static class SpringRestClient_SslNoBundleNameTest { +// +// @Test +// void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { +// RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); +// assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); +// } +// } @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class}, properties = { @@ -212,26 +213,27 @@ void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemC } } - @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, - properties = { - "fluentforms.aem.servername=localhost", - "fluentforms.aem.port=4502", - "fluentforms.aem.user=admin", - "fluentforms.aem.password=admin", - "fluentforms.aem.usessl=true", - "spring.ssl.bundle.jks.aem.truststore.location=file:src/test/resources/aemforms.p12", - "spring.ssl.bundle.jks.aem.truststore.password=Pa$$123", - "spring.ssl.bundle.jks.aem.truststore.type=PKCS12", - "fluentforms.restclient=springrestclient" // Configure for Spring RestClient - }) - public static class SpringRestClient_SslBundleTest { - - @Test - void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { - RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); - assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); - } - } +// TODO: For now we are going to ignore mixed scenarios (where we are using one starter with a different rest client +// @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, +// properties = { +// "fluentforms.aem.servername=localhost", +// "fluentforms.aem.port=4502", +// "fluentforms.aem.user=admin", +// "fluentforms.aem.password=admin", +// "fluentforms.aem.usessl=true", +// "spring.ssl.bundle.jks.aem.truststore.location=file:src/test/resources/aemforms.p12", +// "spring.ssl.bundle.jks.aem.truststore.password=Pa$$123", +// "spring.ssl.bundle.jks.aem.truststore.type=PKCS12", +// "fluentforms.restclient=springrestclient" // Configure for Spring RestClient +// }) +// public static class SpringRestClient_SslBundleTest { +// +// @Test +// void testRestClientFactory(@Autowired RestClientFactory factory, @Autowired AemConfiguration config) { +// RestClient client = factory.apply(toAemConfig(config) , "testRestClientFactory", ()->"correlationId"); +// assertTrue(client instanceof SpringRestClientRestClient, "RestClientFactory should return a SpringRestClientRestClient when configured to do so"); +// } +// } @SpringBootTest(classes = {FluentFormsJerseyAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class}, properties = { diff --git a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/JerseyAutoConfigurationTest.java b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/JerseyAutoConfigurationTest.java index 5cf571aa..02c31155 100644 --- a/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/JerseyAutoConfigurationTest.java +++ b/spring/fluentforms-jersey-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/JerseyAutoConfigurationTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -34,23 +34,27 @@ class JerseyAutoConfigurationTest { * This class provides mock versions of beans that would normally be provided by Spring Boot in a real application. We * only need to mock out the RestClient.Builder and RestClientSsl beans because those are the only Spring Boot provided * beans that our AutoConfigurations depend on. + * + * In theory, we should not need to provide these however spring-boot-starter-jersey brings in spring-boot-restclient + * on to the test classpath which, in turn, triggers the inclusion of the fluent-forms springRestClientFactory which + * requires the mocks. */ private static class SpringBootMocks { @Bean RestClient.Builder mockRestClientBuilder() { return Mockito.mock(RestClient.Builder.class, Mockito.RETURNS_DEEP_STUBS); } @Bean private RestClientSsl mockRestClientSsl() { return Mockito.mock(RestClientSsl.class); } } - private static final AutoConfigurations AUTO_CONFIG = AutoConfigurations.of(FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class, SpringBootMocks.class); + private static final AutoConfigurations AUTO_CONFIG = AutoConfigurations.of(FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, SpringBootMocks.class); - private static final AutoConfigurations LOCAL_SUBMIT_CONFIG = AutoConfigurations.of(FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class, DummyLocalSubmitHandler.class, SpringBootMocks.class); + private static final AutoConfigurations LOCAL_SUBMIT_CONFIG = AutoConfigurations.of(FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, DummyLocalSubmitHandler.class, SpringBootMocks.class); - private static final AutoConfigurations ALTERNATE_PROXY_CONFIG = AutoConfigurations.of(DummyProxyImplementation.class, FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class, SpringBootMocks.class); + private static final AutoConfigurations ALTERNATE_PROXY_CONFIG = AutoConfigurations.of(DummyProxyImplementation.class, FluentFormsJerseyAutoConfiguration.class, AemProxyJerseyAutoConfiguration.class, FluentFormsAutoConfiguration.class, SpringBootMocks.class); // Tests to make sure that only the FluentFormsLibraries are loaded in a non-web application. private static final ContextConsumer FF_LIBRARIES_ONLY = (context) -> { assertAll( - ()->assertThat(context).hasSingleBean(FluentFormsAutoConfiguration.class), - ()->assertThat(context).getBean(FluentFormsAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsAutoConfiguration.class)), + ()->assertThat(context).hasSingleBean(FluentFormsJerseyAutoConfiguration.class), + ()->assertThat(context).getBean(FluentFormsJerseyAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsJerseyAutoConfiguration.class)), ()->assertThat(context).hasSingleBean(OutputService.class), ()->assertThat(context).getBean("outputService").isNotNull(), ()->assertThat(context).doesNotHaveBean(AemProxyAutoConfiguration.class), @@ -64,8 +68,8 @@ private static class SpringBootMocks { // Tests to make sure that only the FluentFormsLibraries are loaded in a web application. private static final ContextConsumer WEB_FF_LIBRARIES_ONLY = (context) -> { assertAll( - ()->assertThat(context).hasSingleBean(FluentFormsAutoConfiguration.class), - ()->assertThat(context).getBean(FluentFormsAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsAutoConfiguration.class)), + ()->assertThat(context).hasSingleBean(FluentFormsJerseyAutoConfiguration.class), + ()->assertThat(context).getBean(FluentFormsJerseyAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsJerseyAutoConfiguration.class)), ()->assertThat(context).hasSingleBean(OutputService.class), ()->assertThat(context).getBean("outputService").isNotNull(), ()->assertThat(context).doesNotHaveBean(AemProxyAutoConfiguration.class), @@ -79,8 +83,8 @@ private static class SpringBootMocks { // Tests to make sure that all beans are loaded by default in a web application. private static final ContextConsumer WEB_ALL_DEFAULT_SERVICES = (context) -> { assertAll( - ()->assertThat(context).hasSingleBean(FluentFormsAutoConfiguration.class), - ()->assertThat(context).getBean(FluentFormsAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsAutoConfiguration.class)), + ()->assertThat(context).hasSingleBean(FluentFormsJerseyAutoConfiguration.class), + ()->assertThat(context).getBean(FluentFormsJerseyAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsJerseyAutoConfiguration.class)), ()->assertThat(context).hasSingleBean(OutputService.class), ()->assertThat(context).getBean("outputService").isNotNull(), ()->assertThat(context).hasSingleBean(AemProxyJerseyAutoConfiguration.class), @@ -92,27 +96,27 @@ private static class SpringBootMocks { }; // Tests to make sure that all beans are loaded by default in a web application. - private static final ContextConsumer WEB_ALL_SPRINGMVC_SERVICES = (context) -> { - assertAll( - ()->assertThat(context).hasSingleBean(FluentFormsAutoConfiguration.class), - ()->assertThat(context).getBean(FluentFormsAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsAutoConfiguration.class)), - ()->assertThat(context).hasSingleBean(OutputService.class), - ()->assertThat(context).getBean("outputService").isNotNull(), - ()->assertThat(context).hasSingleBean(AemProxyAutoConfiguration.class), - ()->assertThat(context).getBean(AemProxyAutoConfiguration.class.getName()).isSameAs(context.getBean(AemProxyAutoConfiguration.class)), - ()->assertThat(context).hasSingleBean(SpringAfSubmitProcessor.class), - ()->assertThat(context).getBean(SpringAfSubmitProcessor.class).isSameAs(context.getBean(AemProxyAfSubmission.AfSubmitAemProxyProcessor.class)), - ()->assertThat(context).doesNotHaveBean(AfSubmissionHandler.class), - ()->assertThat(context).doesNotHaveBean(AemProxyJerseyAutoConfiguration.class), - ()->assertThat(context).doesNotHaveBean(JerseyAfSubmitProcessor.class) - ); - }; +// private static final ContextConsumer WEB_ALL_SPRINGMVC_SERVICES = (context) -> { +// assertAll( +// ()->assertThat(context).hasSingleBean(FluentFormsJerseyAutoConfiguration.class), +// ()->assertThat(context).getBean(FluentFormsJerseyAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsJerseyAutoConfiguration.class)), +// ()->assertThat(context).hasSingleBean(OutputService.class), +// ()->assertThat(context).getBean("outputService").isNotNull(), +// ()->assertThat(context).hasSingleBean(AemProxyAutoConfiguration.class), +// ()->assertThat(context).getBean(AemProxyAutoConfiguration.class.getName()).isSameAs(context.getBean(AemProxyAutoConfiguration.class)), +// ()->assertThat(context).hasSingleBean(SpringAfSubmitProcessor.class), +// ()->assertThat(context).getBean(SpringAfSubmitProcessor.class).isSameAs(context.getBean(AemProxyAfSubmission.AfSubmitAemProxyProcessor.class)), +// ()->assertThat(context).doesNotHaveBean(AfSubmissionHandler.class), +// ()->assertThat(context).doesNotHaveBean(AemProxyJerseyAutoConfiguration.class), +// ()->assertThat(context).doesNotHaveBean(JerseyAfSubmitProcessor.class) +// ); +// }; // Tests to make sure that all beans are loaded in a web application that contains a local submit handler. private static final ContextConsumer WEB_LOCAL_SUBMIT_SERVICES = (context) -> { assertAll( - ()->assertThat(context).hasSingleBean(FluentFormsAutoConfiguration.class), - ()->assertThat(context).getBean(FluentFormsAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsAutoConfiguration.class)), + ()->assertThat(context).hasSingleBean(FluentFormsJerseyAutoConfiguration.class), + ()->assertThat(context).getBean(FluentFormsJerseyAutoConfiguration.class.getName()).isSameAs(context.getBean(FluentFormsJerseyAutoConfiguration.class)), ()->assertThat(context).hasSingleBean(OutputService.class), ()->assertThat(context).getBean("outputService").isNotNull(), ()->assertThat(context).hasSingleBean(AemProxyJerseyAutoConfiguration.class), @@ -222,15 +226,16 @@ void webContext_ProxyEnabled_DefaultProxyConfigured() { .run(WEB_ALL_DEFAULT_SERVICES); } - // All services should start when the jersey proxy type is configured. - @Test - void webContext_ProxyEnabled_SpringMvcProxyConfigured() { - this.webContextRunner - .withPropertyValues("fluentforms.aem.servername=localhost", "fluentforms.aem.port=4502", - "fluentforms.aem.user=user", "fluentforms.aem.password=password", - "fluentforms.rproxy.type=springmvc") - .run(WEB_ALL_SPRINGMVC_SERVICES); - } + // TODO: For now we are going to ignore mixed scenarios (where we are using one starter with a different rest client +// // All services should start when the jersey proxy type is configured. +// @Test +// void webContext_ProxyEnabled_SpringMvcProxyConfigured() { +// this.webContextRunner +// .withPropertyValues("fluentforms.aem.servername=localhost", "fluentforms.aem.port=4502", +// "fluentforms.aem.user=user", "fluentforms.aem.password=password", +// "fluentforms.rproxy.type=springmvc") +// .run(WEB_ALL_SPRINGMVC_SERVICES); +// } public static class DummyLocalSubmitHandler implements AfSubmissionHandler { diff --git a/spring/fluentforms-jersey-spring-boot-starter/pom.xml b/spring/fluentforms-jersey-spring-boot-starter/pom.xml index 02273b72..e7612e08 100644 --- a/spring/fluentforms-jersey-spring-boot-starter/pom.xml +++ b/spring/fluentforms-jersey-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 fluentforms-jersey-spring-boot-starter diff --git a/spring/fluentforms-sample-web-jersey-app/pom.xml b/spring/fluentforms-sample-web-jersey-app/pom.xml index 346d861e..acd3d642 100644 --- a/spring/fluentforms-sample-web-jersey-app/pom.xml +++ b/spring/fluentforms-sample-web-jersey-app/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 com._4point.aem.fluentforms @@ -16,9 +16,8 @@ 17 - 3.13.1 - 3.10.6 - 4.16.0 + 4.0.0-beta.22 + 4.0.8 0.0.5-SNAPSHOT 0.0.4-SNAPSHOT @@ -42,10 +41,6 @@ - - org.springframework.boot - spring-boot-starter - org.springframework.boot spring-boot-starter-jersey @@ -75,7 +70,7 @@ org.springframework.boot - spring-boot-starter-test + spring-boot-starter-jersey-test test @@ -103,7 +98,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/spring/fluentforms-sample-webmvc-app/pom.xml b/spring/fluentforms-sample-webmvc-app/pom.xml index 4dc40985..104af4c0 100644 --- a/spring/fluentforms-sample-webmvc-app/pom.xml +++ b/spring/fluentforms-sample-webmvc-app/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 com._4point.aem.fluentforms @@ -16,9 +16,8 @@ 17 - 3.13.1 - 3.10.6 - 4.16.0 + 4.0.0-beta.22 + 4.0.8 0.0.5-SNAPSHOT 0.0.4-SNAPSHOT @@ -44,16 +43,11 @@ org.springframework.boot - spring-boot-starter + spring-boot-starter-webmvc org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-jersey + spring-boot-starter-restclient com.fasterxml.jackson.core @@ -80,12 +74,23 @@ org.springframework.boot - spring-boot-starter-test + spring-boot-starter-restclient-test + test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + + + org.wiremock + wiremock + ${wiremock.version} test org.wiremock - wiremock-standalone + wiremock-junit5 ${wiremock.version} test @@ -108,7 +113,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/spring/fluentforms-spring-boot-autoconfigure/pom.xml b/spring/fluentforms-spring-boot-autoconfigure/pom.xml index 35838067..2adf25fd 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/pom.xml +++ b/spring/fluentforms-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 com._4point.aem.fluentforms @@ -19,7 +19,8 @@ 3.0.5 0.0.5-SNAPSHOT 0.0.4-SNAPSHOT - 4.0.0-beta.16 + 4.0.0-beta.22 + 4.0.8 1.20.2 1.2.3 @@ -66,7 +67,13 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc + true + provided + + + org.springframework.boot + spring-boot-starter-restclient true provided @@ -80,6 +87,7 @@ rest-services.client ${fluentforms.version} + org.springframework.boot @@ -94,10 +102,22 @@ org.wiremock - wiremock-standalone + wiremock + ${wiremock.version} + test + + + org.wiremock + wiremock-junit5 ${wiremock.version} test + + org.wiremock.integrations + wiremock-spring-boot + ${wiremock-spring-boot.version} + test + org.pitest pitest-junit5-plugin diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmission.java b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmission.java index 3e0a4e65..24948455 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmission.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmission.java @@ -14,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; import org.springframework.boot.ssl.NoSuchSslBundleException; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpEntity; @@ -622,12 +622,15 @@ class ExtractedData { ); } - // Transfer headers from JAX-RS construct to Spring construct (in order to keep JAX-RS encapsulated in this class) + // Transfer headers from WebMVC construct to Spring construct private MultiValueMapAdapter transferHeaders(HttpHeaders headers) { if (logger.isDebugEnabled()) { headers.forEach((k,v)->logger.atDebug().addArgument(k).addArgument(v.size()).log("Found Http header {} with {} values.")); } - return new MultiValueMapAdapter(headers); + // Starting with Spring Framework 7, they do not recommend using MultiValueMap to store HttpHeaders but + // that will require changing the AfSubmissionHandler.Submission interface which has larger implications so, + // for now, we're leaving in the deprecated call. + return new MultiValueMapAdapter(headers.asMultiValueMap()); } // Convert the SubmitResponse object into a JAX-RS Response object. diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfiguration.java b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfiguration.java index 1dd997ac..12998a26 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfiguration.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfiguration.java @@ -1,22 +1,29 @@ package com._4point.aem.fluentforms.spring; import java.util.List; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Fallback; import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.AfSubmissionHandler; +import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.AfSubmitAemProxyProcessor; import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.AfSubmitLocalProcessor; import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.AfSubmitLocalProcessor.InternalAfSubmitAemProxyProcessor; -import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.AfSubmitAemProxyProcessor; import com._4point.aem.fluentforms.spring.AemProxyAfSubmission.SpringAfSubmitProcessor; /** @@ -24,12 +31,17 @@ * resources (.css, .js, etc.) that the browser will request. These requests are forwarded to AEM. */ @AutoConfiguration +@ConditionalOnClass(RestClientSsl.class) @ConditionalOnWebApplication(type=Type.SERVLET) @ConditionalOnProperty(prefix="fluentforms.rproxy", name="enabled", havingValue="true", matchIfMissing=true ) @ConditionalOnProperty(prefix="fluentforms.rproxy", name="type", havingValue="springmvc", matchIfMissing=true ) @EnableConfigurationProperties({AemConfiguration.class, AemProxyConfiguration.class}) @ConditionalOnMissingBean(AemProxyImplemention.class) +@Fallback public class AemProxyAutoConfiguration { + private static final int MINIMUM_PART_COUNT = 20; + private static final String SERVER_TOMCAT_MAX_PART_COUNT = "server.tomcat.max-part-count"; + private final static Logger logger = LoggerFactory.getLogger(AemProxyAutoConfiguration.class); /** * Marker bean to indicate that the Spring MVC-based AEM Proxy implementation is being used. @@ -43,6 +55,30 @@ AemProxyImplemention aemProxyImplemention() { }; } + // Spring Boot 4 lowered the max part count to 10 which is too low for AEM submissions, so we raise it. + @Bean + WebServerFactoryCustomizer webserverFactoryCustomizer() { + return factory->factory.addConnectorCustomizers(c-> + correctMaxPartCount(SERVER_TOMCAT_MAX_PART_COUNT, c.getMaxPartCount(), c::setMaxPartCount) + ); + } + + private static void correctMaxPartCount(String settingName, Integer currentSetting, Consumer settingSetter) { + if (currentSetting >= 0 && currentSetting < MINIMUM_PART_COUNT) { + settingSetter.accept(MINIMUM_PART_COUNT); + logger.atInfo() + .addArgument(settingName) + .addArgument(currentSetting) + .addArgument(MINIMUM_PART_COUNT) + .log("{} changed from {} to {}."); + } else { + logger.atInfo().addArgument(settingName) + .addArgument(currentSetting) + .log("{} remains at {}."); + } + + } + @Bean AemProxyEndpoint aemProxyEndpoint(AemConfiguration aemConfig, AemProxyConfiguration aemProxyConfig, @Autowired(required = false) RestClientSsl restClientSsl) { return new AemProxyEndpoint(aemConfig, aemProxyConfig, restClientSsl); diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyEndpoint.java b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyEndpoint.java index 2804d8f0..668ad132 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyEndpoint.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/AemProxyEndpoint.java @@ -13,7 +13,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; import org.springframework.boot.ssl.NoSuchSslBundleException; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -43,6 +44,7 @@ * get the AdaptiveForm or HTML5 Form using the FLuentForms libraries. * */ +@ConditionalOnClass(RestClientSsl.class) @CrossOrigin @RestController @RequestMapping("/aem") @@ -188,6 +190,16 @@ private static InputStream fixTogglesDotJsonLocation(InputStream is) { return new ReplacingInputStream(is, target, replacement); } + /** + * This function acts as a reverse proxy for anything POSTed to the server. It just forwards + * anything it receives on AEM and then returns the response. It logs slightly differently + * if the request is a form submission. + * + * @param remainder + * @param contentType + * @param in + * @return + */ @PostMapping("/{*remainder}") public ResponseEntity proxyPost(@PathVariable("remainder") String remainder, @RequestHeader(value = "Content-Type", required = false) String contentType, byte[] in) { logger.atDebug().log("Proxying POST request. remainder={}", remainder); @@ -221,8 +233,10 @@ public ResponseEntity proxyPost(@PathVariable("remainder") String remain .log("Returning POST response from target '{}'."); } - return response; - } + return ResponseEntity.status(response.getStatusCode()) + .headers(removeChunkedTransferEncoding(response.getHeaders())) + .body(response.getBody()); +} private static byte[] debugInput(byte[] inputBytes, String target) { logger.atDebug() diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/FluentFormsAutoConfiguration.java b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/FluentFormsAutoConfiguration.java index 00a99df8..9a86dfbc 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/FluentFormsAutoConfiguration.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/FluentFormsAutoConfiguration.java @@ -5,12 +5,14 @@ import java.util.function.Function; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; import org.springframework.boot.ssl.NoSuchSslBundleException; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Fallback; import org.springframework.context.annotation.Lazy; import org.springframework.web.client.RestClient; @@ -69,27 +71,33 @@ private T setAemFields(T builder, AemConfiguration aemConfig ); } - - // matchIfMissing is set to true so that, by default (if nothing is specified in the properties file), then the SpringRestClient is used. - @ConditionalOnProperty(prefix="fluentforms", name="restclient", havingValue="springrestclient", matchIfMissing=true ) - @ConditionalOnMissingBean - @Fallback - @Bean - public RestClientFactory springRestClientFactory(AemConfiguration aemConfig, RestClient.Builder restClientBuilder, RestClientSsl restClientSsl) { - return SpringRestClientRestClient.factory(aemConfig.useSsl() ? restClientBuilder.apply(getSslBundle(aemConfig.sslBundle(), restClientSsl)) - : restClientBuilder - ); // Create a RestClientFactory using Spring RestClient implementation - } - private static Consumer getSslBundle(String sslBundleName, RestClientSsl restClientSsl) { - try { - return restClientSsl.fromBundle(sslBundleName); - } catch (NoSuchSslBundleException e) { - // Default to normal SSL context (which includes the default trust store) - // This is not ideal since misspelling the bundle name silently fails, but is required to avoid breaking existing code. - // At dome point it should probably be changed to let the exception pass and only use the default SSL context - // if the SSL bundle name is empty. - return b->{}; // No-op; + // To prevent class path exceptions when loading just the Jersey libraries (because RestSsl is unavailable) + // we need to wrap the springRestClientFactory in it's own class. + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(RestClientSsl.class) + public static class SpringRestClientFactoryProvider { + // matchIfMissing is set to true so that, by default (if nothing is specified in the properties file), then the SpringRestClient is used. + @ConditionalOnProperty(prefix="fluentforms", name="restclient", havingValue="springrestclient", matchIfMissing=true ) + @ConditionalOnMissingBean + @Fallback + @Bean + public RestClientFactory springRestClientFactory(AemConfiguration aemConfig, RestClient.Builder restClientBuilder, RestClientSsl restClientSsl) { + return SpringRestClientRestClient.factory(aemConfig.useSsl() ? restClientBuilder.apply(getSslBundle(aemConfig.sslBundle(), restClientSsl)) + : restClientBuilder + ); // Create a RestClientFactory using Spring RestClient implementation + } + + private static Consumer getSslBundle(String sslBundleName, RestClientSsl restClientSsl) { + try { + return restClientSsl.fromBundle(sslBundleName); + } catch (NoSuchSslBundleException e) { + // Default to normal SSL context (which includes the default trust store) + // This is not ideal since misspelling the bundle name silently fails, but is required to avoid breaking existing code. + // At dome point it should probably be changed to let the exception pass and only use the default SSL context + // if the SSL bundle name is empty. + return b->{}; // No-op; + } } } diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmissionTest.java b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmissionTest.java index 960760fa..8dfabaeb 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmissionTest.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAfSubmissionTest.java @@ -7,6 +7,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.net.http.HttpClient; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.function.Function; @@ -39,6 +40,7 @@ import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -69,19 +71,14 @@ class AemProxyAfSubmissionTest { private static final String AEM_SUBMIT_ADAPTIVE_FORM_SERVICE_PATH = SUBMIT_ADAPTIVE_FORM_SERVICE_PATH.substring(4); // Same as above minus "/aem" private static final String SAMPLE_RESPONSE_BODY = "body"; -// record JakartaRestClient(WebTarget target, URI uri) {}; -// -// public static JakartaRestClient setUpRestClient(int port) { -// var uri = getBaseUri(port); -// var target = ClientBuilder.newClient() //newClient(clientConfig) -// .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE) // Disable re-directs so that we can test for "thank you page" redirection. -// .register(MultiPartFeature.class) -// .target(uri); -// return new JakartaRestClient(target, uri); -// } - private static RestClient createRestClient(int port) { + // Configure the underlying HttpClient to not follow redirects + HttpClient httpClient = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NEVER) // Use NEVER to prevent all redirects + .build(); + return RestClient.builder() + .requestFactory(new JdkClientHttpRequestFactory(httpClient)) .baseUrl(getBaseUri(port)) .build(); } @@ -160,7 +157,8 @@ public static void main(String[] args) { "fluentforms.aem.useSsl=true", "spring.ssl.bundle.jks.aem.truststore.location=file:src/test/resources/aemforms.p12", "spring.ssl.bundle.jks.aem.truststore.password=Pa$$123", - "spring.ssl.bundle.jks.aem.truststore.type=PKCS12" + "spring.ssl.bundle.jks.aem.truststore.type=PKCS12", + "server.tomcat.max-part-count=" + AemProxyAutoConfigurationTest.MINIMUM_PARTS_COUNT, // Normally supplied by AutoConfiguration } ) public static class AemProxyAfSubmissionTestWithAemAfSubmitProcessorTest { @@ -180,12 +178,10 @@ public static class AemProxyAfSubmissionTestWithAemAfSubmitProcessorTest { @LocalServerPort private int port; -// private JakartaRestClient jrc; private RestClient restClient; @BeforeEach public void setUp() throws Exception { -// jrc = setUpRestClient(port); restClient = createRestClient(port); } @@ -202,16 +198,12 @@ void test() { MultiValueMap> parts = getPdfForm.parts(); ResponseEntity response = restClient.post() .uri(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .contentType(MediaType.MULTIPART_FORM_DATA) .body(parts) .accept(MediaType.APPLICATION_PDF) .retrieve() .toEntity(byte[].class) ; -// .target.path().request().accept(APPLICATION_PDF) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); - // then assertThat(response, allOf(hasStatus(HttpStatus.OK), hasEntityMatching(equalTo(expectedResponseString.getBytes())))); WireMock.verify( @@ -242,7 +234,8 @@ void test() { AemProxyAfSubmissionTestWithLocalAfSubmitProcessorTest.MockSubmissionProcessor2.class} ,properties={ // "debug", - "logging.level.com._4point.aem.fluentforms.spring=DEBUG" + "logging.level.com._4point.aem.fluentforms.spring=DEBUG", + "server.tomcat.max-part-count=" + AemProxyAutoConfigurationTest.MINIMUM_PARTS_COUNT, // Normally supplied by AutoConfiguration } ) public static class AemProxyAfSubmissionTestWithLocalAfSubmitProcessorTest { @@ -253,12 +246,10 @@ public static class AemProxyAfSubmissionTestWithLocalAfSubmitProcessorTest { @LocalServerPort private int port; -// private JakartaRestClient jrc; private RestClient restClient; @BeforeEach public void setUp() throws Exception { -// jrc = setUpRestClient(port); restClient = createRestClient(port); } @@ -276,12 +267,6 @@ void testResponse() { .toEntity(byte[].class) ; -// Response response = jrc.target -// .path(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .request() -// .accept(MediaType.TEXT_PLAIN_TYPE) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); - assertThat(response, allOf(hasStatus(HttpStatus.OK),hasEntityMatching(equalTo(AF_SUBMIT_LOCAL_PROCESSOR_RESPONSE.getBytes())))); } @@ -297,11 +282,6 @@ void testRedirect() { .retrieve() .toBodilessEntity() ; -// Response response = jrc.target -// .path(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .request() -// .accept(MediaType.TEXT_PLAIN_TYPE) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); assertThat(response, allOf(hasStatus(HttpStatus.TEMPORARY_REDIRECT), doesNotHaveEntity())); } @@ -312,17 +292,11 @@ void testSeeOther() { ResponseEntity response = restClient.post() .uri(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .contentType(MediaType.MULTIPART_FORM_DATA) .body(getPdfForm.parts()) .accept(MediaType.TEXT_PLAIN) .retrieve() .toBodilessEntity() ; -// Response response = jrc.target -// .path(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .request() -// .accept(MediaType.TEXT_PLAIN_TYPE) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); assertThat(response, allOf(hasStatus(HttpStatus.SEE_OTHER), doesNotHaveEntity())); } @@ -340,12 +314,6 @@ void testProxy() { .toBodilessEntity() ; -// Response response = jrc.target -// .path(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH+"anythingElse") -// .request() -// .accept(MediaType.TEXT_PLAIN_TYPE) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); - assertThat(response, allOf(hasStatus(HttpStatus.OK), doesNotHaveEntity())); } @@ -418,23 +386,22 @@ public InternalAfSubmitAemProxyProcessor aemProxyProcessor() { */ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = {TestApplication.class, AemProxyAfSubmissionTestWithCustomAfSubmitProcessorTest.MockSubmitProcessor.class} -// ,properties= { -// "debug" -// ,"logging.level.com._4point.aem.fluentforms.spring=DEBUG" -// ,"logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=TRACE" -// } + ,properties= { + "server.tomcat.max-part-count=" + AemProxyAutoConfigurationTest.MINIMUM_PARTS_COUNT, // Normally supplied by AutoConfiguration +// "debug", +// "logging.level.com._4point.aem.fluentforms.spring=DEBUG", +// "logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=TRACE", + } ) public static class AemProxyAfSubmissionTestWithCustomAfSubmitProcessorTest { @LocalServerPort private int port; -// private JakartaRestClient jrc; private RestClient restClient; @BeforeEach public void setUp() throws Exception { -// jrc = setUpRestClient(port); restClient = createRestClient(port); } @@ -445,17 +412,11 @@ void test() { MultiValueMap> parts = getPdfForm.parts(); ResponseEntity response = restClient.post() .uri(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .contentType(MediaType.MULTIPART_FORM_DATA) .body(parts) .accept(MediaType.APPLICATION_PDF) .retrieve() .toEntity(byte[].class) ; -// Response response = jrc.target -// .path(SUBMIT_ADAPTIVE_FORM_SERVICE_PATH) -// .request() -// .accept(APPLICATION_PDF) -// .post(Entity.entity(getPdfForm, getPdfForm.getMediaType())); assertThat(response, allOf(hasStatus(HttpStatus.OK), hasEntityMatching(equalTo(SAMPLE_RESPONSE_BODY.getBytes())))); } diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfigurationTest.java b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfigurationTest.java index 7cafea43..37075518 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfigurationTest.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyAutoConfigurationTest.java @@ -1,22 +1,27 @@ package com._4point.aem.fluentforms.spring; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; -import org.junit.jupiter.api.Test; +import org.apache.catalina.connector.Connector; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.tomcat.TomcatConnectorCustomizer; +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; + -@SpringBootTest(classes = {com._4point.aem.fluentforms.spring.FluentFormsAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class}, -properties = { - "fluentforms.aem.servername=localhost", - "fluentforms.aem.port=4502", - "fluentforms.aem.user=admin", - "fluentforms.aem.password=admin)", - }) class AemProxyAutoConfigurationTest { + static final int MINIMUM_PARTS_COUNT = 20; // TODO: Maybe add more tests here later. // @Test @@ -24,6 +29,71 @@ class AemProxyAutoConfigurationTest { // assertNotNull(afProxyConfigurer); // } + + @SpringBootTest( + classes = {com._4point.aem.fluentforms.spring.FluentFormsAutoConfigurationTest.TestApplication.class, FluentFormsAutoConfiguration.class, AemProxyAutoConfiguration.class}, + properties = { + "fluentforms.aem.servername=localhost", + "fluentforms.aem.port=4502", + "fluentforms.aem.user=admin", + "fluentforms.aem.password=admin)", + }) + + static class AemProxyAutoConfiguration_TomcatWenServerFactory_MaxPartsCount_Test { + + private static TomcatConnectorCustomizer customizer; + private Connector mockConnector = Mockito.mock(Connector.class); + + @BeforeAll + static void setup(@Autowired WebServerFactoryCustomizer webserverFactoryCustomizer) { + assertNotNull(webserverFactoryCustomizer); + customizer = retrieveTomcatCustomizer(webserverFactoryCustomizer); + } + + // This routine emulates the way Spring retrieves the customizer. It ensures that the customizer is + // configured so that Spring can find it. + private static TomcatConnectorCustomizer retrieveTomcatCustomizer(WebServerFactoryCustomizer webserverFactoryCustomizer) { + TomcatServletWebServerFactory tomcatFactory = mock(TomcatServletWebServerFactory.class); + ArgumentCaptor customizerCaptor = ArgumentCaptor.forClass(TomcatConnectorCustomizer.class); + doNothing().when(tomcatFactory).addConnectorCustomizers(customizerCaptor.capture()); + webserverFactoryCustomizer.customize(tomcatFactory); + return customizerCaptor.getValue(); + } + + @ParameterizedTest + @DisplayName("If greater than or equal to the minimum, value should be left unaltered.") + @ValueSource(ints = {30, -1, MINIMUM_PARTS_COUNT}) // All greater than or equal to the minimum + void testTomcatMaxPartCountSetting_NoChange(int getValue) { + // Given: Mock just the get() + when(mockConnector.getMaxPartCount()).thenReturn(getValue); + + // When + customizer.customize(mockConnector); + + //Then + verify(mockConnector).getMaxPartCount(); + verify(mockConnector, Mockito.times(0)).setMaxPartCount(Mockito.anyInt()); + } + + @ParameterizedTest + @DisplayName("If lower than the minimum, value should be set to minimum.") + @ValueSource(ints = {0, 10}) // All lower than minimum + void testTomcatMaxPartCountSetting(int getValue) { + // Given: Mock the get() and the set() + when(mockConnector.getMaxPartCount()).thenReturn(getValue); + ArgumentCaptor updatedMaxPartCountCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing().when(mockConnector).setMaxPartCount(updatedMaxPartCountCaptor.capture()); + + // When + customizer.customize(mockConnector ); + + // Then + verify(mockConnector).getMaxPartCount(); + verify(mockConnector).setMaxPartCount(MINIMUM_PARTS_COUNT); + assertEquals(updatedMaxPartCountCaptor.getValue(), MINIMUM_PARTS_COUNT); + } + } + @SpringBootApplication @EnableConfigurationProperties({AemConfiguration.class,AemProxyConfiguration.class}) public static class TestApplication { diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyEndpointTest.java b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyEndpointTest.java index 5b19f7da..d2a4bbbd 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyEndpointTest.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AemProxyEndpointTest.java @@ -23,16 +23,17 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.web.client.RestClient; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; -@WireMockTest(httpPort = AemProxyEndpointTest.WIREMOCK_PORT) +@WireMockTest() @SpringBootTest(classes = {com._4point.aem.fluentforms.spring.AemProxyEndpointTest.TestApplication.class}, webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "fluentforms.aem.servername=localhost", -"fluentforms.aem.port=" + AemProxyEndpointTest.WIREMOCK_PORT, "fluentforms.aem.user=ENC(7FgD3ZsSExfUGRYlXNc++6C1upPBURNKq6HouzagnNZW4FsBwFs5+crawv+djhw6)", "fluentforms.aem.password=ENC(QmQ6iTm/+TOO8U3dDuBzJWH129vReWgYNdgqQwWhjWaQy6j8sMnk2/Auhehmlh3v)", //"fluentforms.aem.useSsl=true", @@ -41,13 +42,18 @@ "jasypt.encryptor.password=4Point", "jasypt.encryptor.iv-generator-classname=org.jasypt.iv.RandomIvGenerator", "jasypt.encryptor.salt-generator-classname=org.jasypt.salt.RandomSaltGenerator", -"logging.level.com._4point.aem.fluentforms.spring.AemProxyEndpoint=DEBUG" +"logging.level.com._4point.aem.fluentforms.spring.AemProxyEndpoint=DEBUG", +//Wiremock produces a lot of output, the following entries reduce that output. They can be removed for debugging. +"logging.level.org.wiremock.spring=WARN", "logging.level.WireMock.wiremock=WARN", }) +@EnableWireMock(@ConfigureWireMock( + portProperties = "fluentforms.aem.port" + ) + ) @Timeout(value = 5, unit = TimeUnit.MINUTES) // Fail tests that take longer than this to prevent hanging. class AemProxyEndpointTest { private final static Logger logger = LoggerFactory.getLogger(AemProxyEndpointTest.class); - static final int WIREMOCK_PORT = 5504; static final String AF_BASE_LOCATION = "/aem"; // The following is a string that contains all possible values that may be modified by the AemProxyEndpoint. diff --git a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AutoConfigurationTest.java b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AutoConfigurationTest.java index 1d1bf50e..dc78e3a9 100644 --- a/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AutoConfigurationTest.java +++ b/spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/AutoConfigurationTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientSsl; +import org.springframework.boot.restclient.autoconfigure.RestClientSsl; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/spring/fluentforms-spring-boot-starter/pom.xml b/spring/fluentforms-spring-boot-starter/pom.xml index f91e248f..b42746fa 100644 --- a/spring/fluentforms-spring-boot-starter/pom.xml +++ b/spring/fluentforms-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.7 + 4.0.0 fluentforms-spring-boot-starter