diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactory.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactory.java index 1a1efc8d224..5b94c08a047 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactory.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactory.java @@ -32,29 +32,34 @@ import org.springframework.web.util.UriComponentsBuilder; * {@link UriBuilderFactory} to support {@link BaseUrl}. * * @author Phillip Webb + * @author Stephane Nicoll * @since 4.0.0 */ -public class BaseUrlUriBuilderFactory implements UriBuilderFactory { - - private final UriBuilderFactory delegate; +public final class BaseUrlUriBuilderFactory implements UriBuilderFactory { private final BaseUrl baseUrl; /** * Create a new {@link BaseUrlUriBuilderFactory} instance. - * @param delegate the delegate {@link UriBuilderFactory} * @param baseUrl the base URL to use */ - public BaseUrlUriBuilderFactory(UriBuilderFactory delegate, BaseUrl baseUrl) { - Assert.notNull(delegate, "'delegate' must not be null"); + BaseUrlUriBuilderFactory(BaseUrl baseUrl) { Assert.notNull(baseUrl, "'baseUrl' must not be null"); - this.delegate = delegate; this.baseUrl = baseUrl; } + /** + * Get a {@link UriBuilderFactory} instance applying the given {@code baseUrl}. + * @param baseUrl the base URL to apply or {@code null} + * @return a factory for the given base URL + */ + public static UriBuilderFactory get(@Nullable BaseUrl baseUrl) { + return (baseUrl != null) ? new BaseUrlUriBuilderFactory(baseUrl) : new DefaultUriBuilderFactory(); + } + @Override public UriBuilder uriString(String uriTemplate) { - return UriComponentsBuilder.fromUriString(apply(uriTemplate)); + return createDelegate().uriString(uriTemplate); } @Override @@ -64,21 +69,16 @@ public class BaseUrlUriBuilderFactory implements UriBuilderFactory { @Override public URI expand(String uriTemplate, Map uriVariables) { - return this.delegate.expand(apply(uriTemplate), uriVariables); + return createDelegate().expand(uriTemplate, uriVariables); } @Override public URI expand(String uriTemplate, @Nullable Object... uriVariables) { - return this.delegate.expand(apply(uriTemplate), uriVariables); + return createDelegate().expand(uriTemplate, uriVariables); } - String apply(String uriTemplate) { - return (uriTemplate.startsWith("/")) ? this.baseUrl.resolve(uriTemplate) : uriTemplate; - } - - public static UriBuilderFactory get(@Nullable BaseUrl baseUrl) { - DefaultUriBuilderFactory delegate = new DefaultUriBuilderFactory(); - return (baseUrl != null) ? new BaseUrlUriBuilderFactory(delegate, baseUrl) : delegate; + private UriBuilderFactory createDelegate() { + return this.baseUrl.getUriBuilderFactory(); } } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java index 97b9d9832c6..718fcbacea3 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/http/server/BaseUrl.java @@ -18,10 +18,10 @@ package org.springframework.boot.test.http.server; import java.util.function.Supplier; -import org.jspecify.annotations.Nullable; - import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.UriBuilderFactory; /** * A base URL that can be used to connect to the running server. @@ -44,29 +44,10 @@ public interface BaseUrl { boolean isHttps(); /** - * Resolve the URL to a string. This method is called as late as possible to ensure - * that an local port information is available. - * @param path the path to append - * @return the resolved base URL + * Get a {@link UriBuilderFactory} that applies the base URL. + * @return a {@link UriBuilderFactory} */ - default String resolve(@Nullable String path) { - String resolved = resolve(); - if (StringUtils.hasLength(path)) { - if (resolved.endsWith("/") && path.startsWith("/")) { - path = path.substring(1); - } - resolved += (resolved.endsWith("/") || path.startsWith("/")) ? "" : "/"; - resolved += path; - } - return resolved; - } - - /** - * Resolve the URL to a string. This method is called as late as possible to ensure - * that an local port information is available. - * @return the resolved base URL - */ - String resolve(); + UriBuilderFactory getUriBuilderFactory(); /** * Return a new instance that applies the given {@code path}. @@ -113,7 +94,11 @@ public interface BaseUrl { } @Override - public String resolve() { + public UriBuilderFactory getUriBuilderFactory() { + return new DefaultUriBuilderFactory(resolve()); + } + + String resolve() { return this.resolver.get(); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java index d7839a1c6b7..9c3f7ec9885 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebClient.java @@ -43,8 +43,8 @@ public class BaseUrlWebClient extends WebClient { @Override public

P getPage(String url) throws IOException, FailingHttpStatusCodeException { - if (this.baseUrl != null && url.startsWith("/")) { - url = this.baseUrl.resolve(url); + if (this.baseUrl != null) { + url = this.baseUrl.getUriBuilderFactory().uriString(url).toUriString(); } return super.getPage(url); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java index 32aaefd218e..b6589e345b2 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/BaseUrlWebConnectionHtmlUnitDriver.java @@ -56,8 +56,8 @@ public class BaseUrlWebConnectionHtmlUnitDriver extends WebConnectionHtmlUnitDri @Override public void get(String url) { - if (this.baseUrl != null && url.startsWith("/")) { - url = this.baseUrl.resolve(url); + if (this.baseUrl != null) { + url = this.baseUrl.getUriBuilderFactory().uriString(url).toUriString(); } super.get(url); } diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactoryTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactoryTests.java new file mode 100644 index 00000000000..55769d38af8 --- /dev/null +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/client/BaseUrlUriBuilderFactoryTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.http.client; + +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.http.server.BaseUrl; +import org.springframework.web.util.UriBuilderFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link BaseUrlUriBuilderFactory}. + * + * @author Stephane Nicoll + */ +class BaseUrlUriBuilderFactoryTests { + + @Test + void uriWithRootSlashAddsBaseUrl() { + UriBuilderFactory factory = BaseUrlUriBuilderFactory.get(BaseUrl.of("https://example.com")); + assertThat(factory.uriString("/").build()).isEqualTo(URI.create("https://example.com/")); + } + + @Test + void uriWithEmptyAddsBaseUrl() { + UriBuilderFactory factory = BaseUrlUriBuilderFactory.get(BaseUrl.of("https://example.com")); + assertThat(factory.uriString("").build()).isEqualTo(URI.create("https://example.com")); + } + + @Test + void uriWithMapVariablesAddsBaseUrl() { + UriBuilderFactory factory = BaseUrlUriBuilderFactory.get(BaseUrl.of("https://example.com")); + assertThat(factory.expand("/test/{name}", Map.of("name", "value"))) + .isEqualTo(URI.create("https://example.com/test/value")); + } + + @Test + void uriWithVariablesAddsBaseUrl() { + UriBuilderFactory factory = BaseUrlUriBuilderFactory.get(BaseUrl.of("https://example.com")); + assertThat(factory.expand("/test/{name}", "value")).isEqualTo(URI.create("https://example.com/test/value")); + } + + @Test + void uriWithHostDoesNotExpandBaseUrl() { + UriBuilderFactory factory = BaseUrlUriBuilderFactory.get(BaseUrl.of("https://example.com")); + assertThat(factory.uriString("https://sub.example.com").build()) + .isEqualTo(URI.create("https://sub.example.com")); + } + +} diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java index 25e86ad9b67..90e33dca2a3 100644 --- a/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/http/server/BaseUrlTests.java @@ -32,33 +32,29 @@ class BaseUrlTests { @Test void resolveWithString() { - assertThat(BaseUrl.of("http://localhost").resolve(null)).isEqualTo("http://localhost"); - assertThat(BaseUrl.of("http://localhost").resolve("")).isEqualTo("http://localhost"); - assertThat(BaseUrl.of("http://localhost").resolve("path")).isEqualTo("http://localhost/path"); - assertThat(BaseUrl.of("http://localhost").resolve("/path")).isEqualTo("http://localhost/path"); - assertThat(BaseUrl.of("http://localhost/").resolve("path")).isEqualTo("http://localhost/path"); - assertThat(BaseUrl.of("http://localhost/").resolve("/path")).isEqualTo("http://localhost/path"); + assertThat(resolve(BaseUrl.of("http://localhost"), "")).isEqualTo("http://localhost"); + assertThat(resolve(BaseUrl.of("http://localhost"), "/path")).isEqualTo("http://localhost/path"); + assertThat(resolve(BaseUrl.of("http://localhost/"), "/path")).isEqualTo("http://localhost/path"); } @Test void ofWhenHttp() { BaseUrl baseUrl = BaseUrl.of("http://localhost:8080/context"); assertThat(baseUrl.isHttps()).isFalse(); - assertThat(baseUrl.resolve()).isEqualTo("http://localhost:8080/context"); + assertThat(resolve(baseUrl, "")).isEqualTo("http://localhost:8080/context"); } @Test void ofWhenHttps() { BaseUrl baseUrl = BaseUrl.of("https://localhost:8080/context"); assertThat(baseUrl.isHttps()).isTrue(); - assertThat(baseUrl.resolve()).isEqualTo("https://localhost:8080/context"); + assertThat(resolve(baseUrl, "")).isEqualTo("https://localhost:8080/context"); } @Test void ofWhenUppercaseHttps() { BaseUrl baseUrl = BaseUrl.of("HTTPS://localhost:8080/context"); assertThat(baseUrl.isHttps()).isTrue(); - assertThat(baseUrl.resolve()).isEqualTo("HTTPS://localhost:8080/context"); } @Test @@ -73,8 +69,8 @@ class BaseUrlTests { BaseUrl baseUrl = BaseUrl.of(true, () -> String.valueOf(atomicInteger.incrementAndGet())); assertThat(atomicInteger.get()).isZero(); assertThat(baseUrl.isHttps()).isTrue(); - assertThat(baseUrl.resolve()).isEqualTo("1"); - assertThat(baseUrl.resolve()).isEqualTo("2"); + assertThat(resolve(baseUrl, "")).isEqualTo("1"); + assertThat(resolve(baseUrl, "")).isEqualTo("2"); } @Test @@ -87,19 +83,22 @@ class BaseUrlTests { @Test void withPath() { BaseUrl baseUrl = BaseUrl.of("http://localhost"); - assertThat(baseUrl.withPath("/context").resolve("")).isEqualTo("http://localhost/context"); - assertThat(baseUrl.withPath("/context").withPath("/test").resolve("path")) + assertThat(resolve(baseUrl.withPath("/context"), "")).isEqualTo("http://localhost/context"); + assertThat(resolve(baseUrl.withPath("/context").withPath("/test"), "/path")) .isEqualTo("http://localhost/context/test/path"); } @Test void withPathInvokesParentResolver() { AtomicInteger atomicInteger = new AtomicInteger(); - BaseUrl baseUrl = BaseUrl.of(true, - () -> "https://example.com/" + atomicInteger.incrementAndGet()); - assertThat(baseUrl.withPath("/context").resolve("")).isEqualTo("https://example.com/1/context"); - assertThat(baseUrl.withPath("/context").withPath("/test").resolve("path")) - .isEqualTo("https://example.com/2/context/test/path"); + BaseUrl baseUrl = BaseUrl.of(true, () -> "https://example.com/" + atomicInteger.incrementAndGet()); + assertThat(resolve(baseUrl.withPath("/context"), "")).isEqualTo("https://example.com/1/context"); + assertThat(resolve(baseUrl.withPath("/context").withPath("/test"), "/path")) + .isEqualTo("https://example.com/2/context/test/path"); + } + + private String resolve(BaseUrl baseUrl, String path) { + return baseUrl.getUriBuilderFactory().uriString(path).toUriString(); } }