diff --git a/exonum-light-client/CHANGELOG.md b/exonum-light-client/CHANGELOG.md index d5d86fec17..a75cde3af5 100644 --- a/exonum-light-client/CHANGELOG.md +++ b/exonum-light-client/CHANGELOG.md @@ -16,8 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased ### Added -* `ExonumClient#findNonEmptyBlocks` to find a certain number of the most recent non-empty +- `ExonumClient#findNonEmptyBlocks` to find a certain number of the most recent non-empty blocks (from the last block and up to the genesis block). (#953) +- Prefix URL can be set for routing all Light Client requests. (#997) ### Changed - `ExonumClient#getBlocks` accepts a closed range of block heights _[from; to]_ @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `ExonumClient#getLastBlocks` returns only the blocks from the closed range _[max(0, blockchainHeight - count + 1), blockchainHeight]_. The size of the range _(min(count, blockchainHeight + 1))_ is no longer limited. (#953) +- Now port is optional in the Exonum host URL. (#997) ## 0.2.0 - 2019-05-27 diff --git a/exonum-light-client/src/main/java/com/exonum/client/ExonumClient.java b/exonum-light-client/src/main/java/com/exonum/client/ExonumClient.java index 262d2cd701..f9cf93d751 100644 --- a/exonum-light-client/src/main/java/com/exonum/client/ExonumClient.java +++ b/exonum-light-client/src/main/java/com/exonum/client/ExonumClient.java @@ -201,6 +201,7 @@ class Builder { private URL exonumHost; private OkHttpClient httpClient = DEFAULT_CLIENT; + private String prefix = ""; /** * Sets Exonum host url. @@ -235,13 +236,23 @@ public Builder setHttpClient(OkHttpClient client) { return this; } + /** + * Sets an optional URL prefix to be applied to all requests made by the client. + * Can be helpful in case of using middleware routing proxy on the blockchain node side. + * There is no prefix by default. + */ + public Builder setPrefix(String prefix) { + this.prefix = checkNotNull(prefix); + return this; + } + /** * Creates Exonum client instance. * @throws IllegalStateException if required fields weren't set */ public ExonumClient build() { checkRequiredFieldsSet(); - return new ExonumHttpClient(httpClient, exonumHost); + return new ExonumHttpClient(httpClient, exonumHost, prefix); } private void checkRequiredFieldsSet() { diff --git a/exonum-light-client/src/main/java/com/exonum/client/ExonumHttpClient.java b/exonum-light-client/src/main/java/com/exonum/client/ExonumHttpClient.java index 7b0d2160bc..e22981bf96 100644 --- a/exonum-light-client/src/main/java/com/exonum/client/ExonumHttpClient.java +++ b/exonum-light-client/src/main/java/com/exonum/client/ExonumHttpClient.java @@ -19,12 +19,12 @@ import static com.exonum.client.ExonumApi.MAX_BLOCKS_PER_REQUEST; import static com.exonum.client.ExonumIterables.indexOf; -import static com.exonum.client.ExonumUrls.BLOCK; import static com.exonum.client.ExonumUrls.BLOCKS; import static com.exonum.client.ExonumUrls.HEALTH_CHECK; import static com.exonum.client.ExonumUrls.MEMORY_POOL; import static com.exonum.client.ExonumUrls.TRANSACTIONS; import static com.exonum.client.ExonumUrls.USER_AGENT; +import static com.exonum.client.HttpUrlHelper.getFullUrl; import static com.exonum.client.request.BlockFilteringOption.INCLUDE_EMPTY; import static com.exonum.client.request.BlockFilteringOption.SKIP_EMPTY; import static com.exonum.client.request.BlockTimeOption.INCLUDE_COMMIT_TIME; @@ -34,6 +34,7 @@ import static java.lang.Math.max; import static java.lang.Math.min; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.util.Collections.emptyMap; import com.exonum.binding.common.hash.HashCode; import com.exonum.binding.common.message.TransactionMessage; @@ -46,6 +47,7 @@ import com.exonum.client.response.HealthCheckInfo; import com.exonum.client.response.TransactionResponse; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.io.IOException; import java.net.URL; @@ -72,15 +74,17 @@ class ExonumHttpClient implements ExonumClient { private final OkHttpClient httpClient; private final URL exonumHost; + private final String prefix; - ExonumHttpClient(OkHttpClient httpClient, URL exonumHost) { + ExonumHttpClient(OkHttpClient httpClient, URL exonumHost, String prefix) { this.httpClient = httpClient; this.exonumHost = exonumHost; + this.prefix = prefix; } @Override public HashCode submitTransaction(TransactionMessage transactionMessage) { - Request request = post(toFullUrl(TRANSACTIONS), + Request request = post(url(TRANSACTIONS), ExplorerApiHelper.createSubmitTxBody(transactionMessage)); return blockingExecuteAndParse(request, ExplorerApiHelper::parseSubmitTxResponse); @@ -88,21 +92,21 @@ public HashCode submitTransaction(TransactionMessage transactionMessage) { @Override public int getUnconfirmedTransactionsCount() { - Request request = get(toFullUrl(MEMORY_POOL)); + Request request = get(url(MEMORY_POOL)); return blockingExecuteAndParse(request, SystemApiHelper::parseMemoryPoolJson); } @Override public HealthCheckInfo healthCheck() { - Request request = get(toFullUrl(HEALTH_CHECK)); + Request request = get(url(HEALTH_CHECK)); return blockingExecuteAndParse(request, SystemApiHelper::parseHealthCheckJson); } @Override public String getUserAgentInfo() { - Request request = get(toFullUrl(USER_AGENT)); + Request request = get(url(USER_AGENT)); return blockingExecutePlainText(request); } @@ -110,11 +114,8 @@ public String getUserAgentInfo() { @Override public Optional getTransaction(HashCode id) { HashCode hash = checkNotNull(id); - HttpUrl url = urlBuilder() - .encodedPath(TRANSACTIONS) - .addQueryParameter("hash", hash.toString()) - .build(); - Request request = get(url); + Map query = ImmutableMap.of("hash", hash.toString()); + Request request = get(url(TRANSACTIONS, query)); return blockingExecute(request, response -> { if (response.code() == HTTP_NOT_FOUND) { @@ -140,11 +141,8 @@ public long getBlockchainHeight() { @Override public BlockResponse getBlockByHeight(long height) { checkArgument(0 <= height, "Height can't be negative, but was %s", height); - HttpUrl url = urlBuilder() - .encodedPath(BLOCK) - .addQueryParameter("height", String.valueOf(height)) - .build(); - Request request = get(url); + Map query = ImmutableMap.of("height", String.valueOf(height)); + Request request = get(url(BLOCKS, query)); return blockingExecuteAndParse(request, ExplorerApiHelper::parseGetBlockResponse); } @@ -282,11 +280,7 @@ private BlocksResponse doGetBlocks(int count, BlockFilteringOption blockFilter, if (heightMax != null) { query.put("latest", String.valueOf(heightMax)); } - - HttpUrl.Builder httpRequest = urlBuilder().encodedPath(BLOCKS); - query.forEach(httpRequest::addQueryParameter); - - Request request = get(httpRequest.build()); + Request request = get(url(BLOCKS, query)); return blockingExecuteAndParse(request, ExplorerApiHelper::parseGetBlocksResponse); } @@ -305,18 +299,12 @@ private static Request post(HttpUrl url, String jsonBody) { .build(); } - private HttpUrl toFullUrl(String relativeUrl) { - return urlBuilder() - .encodedPath(relativeUrl) - .build(); + private HttpUrl url(String path, Map query) { + return getFullUrl(exonumHost, prefix, path, query); } - private HttpUrl.Builder urlBuilder() { - - return new HttpUrl.Builder() - .scheme(exonumHost.getProtocol()) - .host(exonumHost.getHost()) - .port(exonumHost.getPort()); + private HttpUrl url(String path) { + return url(path, emptyMap()); } private T blockingExecute(Request request, Function responseHandler) { diff --git a/exonum-light-client/src/main/java/com/exonum/client/ExonumUrls.java b/exonum-light-client/src/main/java/com/exonum/client/ExonumUrls.java index 2a76e683f1..3f3df57ec7 100644 --- a/exonum-light-client/src/main/java/com/exonum/client/ExonumUrls.java +++ b/exonum-light-client/src/main/java/com/exonum/client/ExonumUrls.java @@ -21,8 +21,8 @@ * Contains Exonum API URLs. */ final class ExonumUrls { - private static final String EXPLORER_PATHS_PREFIX = "/api/explorer/v1"; - private static final String SYS_PATHS_PREFIX = "/api/system/v1"; + private static final String EXPLORER_PATHS_PREFIX = "api/explorer/v1"; + private static final String SYS_PATHS_PREFIX = "api/system/v1"; static final String TRANSACTIONS = EXPLORER_PATHS_PREFIX + "/transactions"; static final String BLOCK = EXPLORER_PATHS_PREFIX + "/block"; static final String BLOCKS = EXPLORER_PATHS_PREFIX + "/blocks"; diff --git a/exonum-light-client/src/main/java/com/exonum/client/HttpUrlHelper.java b/exonum-light-client/src/main/java/com/exonum/client/HttpUrlHelper.java new file mode 100644 index 0000000000..1e2d710af4 --- /dev/null +++ b/exonum-light-client/src/main/java/com/exonum/client/HttpUrlHelper.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.client; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URL; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.HttpUrl.Builder; + +final class HttpUrlHelper { + + static HttpUrl getFullUrl(URL host, String prefix, String relativeUrl, + Map encodedQueryParameters) { + checkNotNull(encodedQueryParameters); + Builder urlBuilder = urlHostBuilder(host) + .addPathSegments(prefix) + .addPathSegments(relativeUrl); + encodedQueryParameters.forEach(urlBuilder::addEncodedQueryParameter); + + return urlBuilder.build(); + } + + private static HttpUrl.Builder urlHostBuilder(URL host) { + checkNotNull(host); + HttpUrl.Builder builder = new HttpUrl.Builder() + .scheme(host.getProtocol()) + .host(host.getHost()); + if (host.getPort() != -1) { + builder.port(host.getPort()); + } + return builder; + } + + private HttpUrlHelper() { + } +} diff --git a/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientBlocksIntegrationTest.java b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientBlocksIntegrationTest.java index 5939c70b8a..ad44148b65 100644 --- a/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientBlocksIntegrationTest.java +++ b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientBlocksIntegrationTest.java @@ -33,6 +33,7 @@ import static com.exonum.client.ExonumApi.MAX_BLOCKS_PER_REQUEST; import static com.exonum.client.ExonumUrls.BLOCK; import static com.exonum.client.ExonumUrls.BLOCKS; +import static com.exonum.client.TestUtils.assertPath; import static com.exonum.client.request.BlockFilteringOption.INCLUDE_EMPTY; import static com.exonum.client.request.BlockFilteringOption.SKIP_EMPTY; import static com.exonum.client.request.BlockTimeOption.INCLUDE_COMMIT_TIME; @@ -50,7 +51,6 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -123,7 +123,7 @@ void getBlockByHeight() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCK)); + assertPath(recordedRequest, BLOCK); assertThat(recordedRequest.getRequestUrl().queryParameter("height"), is(String.valueOf(height))); } @@ -160,7 +160,7 @@ void getBlocksSinglePageSkippingEmpty() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1); assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption); } @@ -197,7 +197,7 @@ void getBlocksSinglePageSkippingEmptyFiltersOutOfRangeBlocks() throws Interrupte // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1); assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption); } @@ -228,7 +228,7 @@ void getBlocksSinglePageNoTime() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); int expectedNumBlocks = Math.toIntExact(toHeight - fromHeight + 1); assertBlockRequestParams(recordedRequest, expectedNumBlocks, blockFilter, toHeight, timeOption); } @@ -400,7 +400,7 @@ void getLastBlocksSkippingEmptySinglePage() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); assertBlockRequestParams(recordedRequest, numBlocks, blockFilter, null, timeOption); } @@ -437,7 +437,7 @@ void getLastBlocksSkippingEmptyMoreThanCommitted(int overflow) // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); int expectedFirstRequestSize = min(blocksCount, MAX_BLOCKS_PER_REQUEST); assertBlockRequestParams(recordedRequest, expectedFirstRequestSize, blockFilter, null, timeOption); @@ -659,7 +659,7 @@ void getLastBlock() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); assertBlockRequestParams(recordedRequest, 1, INCLUDE_EMPTY, null, INCLUDE_COMMIT_TIME); } @@ -685,7 +685,7 @@ void getLastNotEmptyBlock() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); assertBlockRequestParams(recordedRequest, 1, SKIP_EMPTY, null, INCLUDE_COMMIT_TIME); } @@ -710,7 +710,7 @@ void getLastNotEmptyBlockNoBlock() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); assertBlockRequestParams(recordedRequest, 1, SKIP_EMPTY, null, INCLUDE_COMMIT_TIME); } @@ -738,7 +738,7 @@ void getBlockchainHeight() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(BLOCKS)); + assertPath(recordedRequest, BLOCKS); assertBlockRequestParams(recordedRequest, 0, INCLUDE_EMPTY, null, NO_COMMIT_TIME); } diff --git a/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientIntegrationTest.java b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientIntegrationTest.java index 134f827325..c9d2e57a94 100644 --- a/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientIntegrationTest.java +++ b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientIntegrationTest.java @@ -23,12 +23,13 @@ import static com.exonum.client.ExonumUrls.MEMORY_POOL; import static com.exonum.client.ExonumUrls.TRANSACTIONS; import static com.exonum.client.ExonumUrls.USER_AGENT; +import static com.exonum.client.TestUtils.assertExactPath; +import static com.exonum.client.TestUtils.assertPath; import static com.exonum.client.TestUtils.createTransactionMessage; import static com.exonum.client.TestUtils.toHex; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -91,7 +92,7 @@ void submitTransactionTest() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("POST")); - assertThat(recordedRequest.getPath(), is(TRANSACTIONS)); + assertExactPath(recordedRequest, TRANSACTIONS); // Assert request encoding String json = recordedRequest.getBody().readUtf8(); @@ -117,7 +118,7 @@ void getUnconfirmedTransactions() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), is(MEMORY_POOL)); + assertExactPath(recordedRequest, MEMORY_POOL); } @Test @@ -136,7 +137,7 @@ void healthCheck() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), is(HEALTH_CHECK)); + assertExactPath(recordedRequest, HEALTH_CHECK); } @Test @@ -154,7 +155,7 @@ void getUserAgentInfo() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), is(USER_AGENT)); + assertExactPath(recordedRequest, USER_AGENT); } @Test @@ -189,7 +190,7 @@ void getTransaction() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(TRANSACTIONS)); + assertPath(recordedRequest, TRANSACTIONS); assertThat(recordedRequest.getRequestUrl().queryParameter("hash"), is(id.toString())); } @@ -208,7 +209,7 @@ void getTransactionNotFound() throws InterruptedException { // Assert request params RecordedRequest recordedRequest = server.takeRequest(); assertThat(recordedRequest.getMethod(), is("GET")); - assertThat(recordedRequest.getPath(), startsWith(TRANSACTIONS)); + assertPath(recordedRequest, TRANSACTIONS); assertThat(recordedRequest.getRequestUrl().queryParameter("hash"), is(id.toString())); } diff --git a/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientWithPrefixIntegrationTest.java b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientWithPrefixIntegrationTest.java new file mode 100644 index 0000000000..97aa715049 --- /dev/null +++ b/exonum-light-client/src/test/java/com/exonum/client/ExonumHttpClientWithPrefixIntegrationTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.client; + +import static com.exonum.client.TestUtils.assertPath; + +import java.io.IOException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ExonumHttpClientWithPrefixIntegrationTest { + private MockWebServer server; + private ExonumClient exonumClient; + private String prefixUrl = "pre/fix"; + + @BeforeEach + void start() throws IOException { + server = new MockWebServer(); + server.start(); + + exonumClient = ExonumClient.newBuilder() + .setExonumHost(server.url("/").url()) + .setPrefix(prefixUrl) + .build(); + } + + @AfterEach + void shutdown() throws IOException { + server.shutdown(); + } + + @Test + @DisplayName("LC applies the given prefix to the underlying requests") + void requestWithPrefix() throws InterruptedException { + // Mock response + server.enqueue(new MockResponse().setBody("ok")); + + // Call + exonumClient.getUserAgentInfo(); + + // Assert request params + RecordedRequest recordedRequest = server.takeRequest(); + assertPath(recordedRequest, prefixUrl); + } + +} diff --git a/exonum-light-client/src/test/java/com/exonum/client/HttpUrlHelperTest.java b/exonum-light-client/src/test/java/com/exonum/client/HttpUrlHelperTest.java new file mode 100644 index 0000000000..8622468a90 --- /dev/null +++ b/exonum-light-client/src/test/java/com/exonum/client/HttpUrlHelperTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 The Exonum Team + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exonum.client; + +import static java.util.Collections.emptyMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class HttpUrlHelperTest { + + @ParameterizedTest + @MethodSource("source") + void getFullUrl(String expectedUrl, + String host, String prefix, String path, Map query) + throws MalformedURLException { + HttpUrl url = HttpUrlHelper.getFullUrl(new URL(host), prefix, path, query); + + assertThat(url.toString(), is(expectedUrl)); + } + + /** + * Provides a combination of parameters by the following rules: + * - port is optional + * - prefix is optional + * - paths can start either with or without heading slash + * - query params are optional. + */ + private static List source() { + Map noQuery = emptyMap(); + return ImmutableList.of( + arguments("http://localhost//path/to/source", + "http://localhost", "", "/path/to/source", noQuery), + arguments("http://localhost/prefix/path/to/source", + "http://localhost", "prefix", "path/to/source", noQuery), + arguments("http://localhost/pre%20fix/path/to/source", + "http://localhost", "pre fix", "path/to/source", noQuery), + arguments("http://localhost:8080//prefix//path/to/source", + "http://localhost:8080", "/prefix", "/path/to/source", noQuery), + arguments("http://localhost:8080//pre/fix//path/to/source", + "http://localhost:8080", "/pre/fix", "/path/to/source", noQuery), + arguments("http://localhost:8080//pre/fix//path/to/source?key=value", + "http://localhost:8080", "/pre/fix", "/path/to/source", ImmutableMap.of("key", "value")) + ); + } +} diff --git a/exonum-light-client/src/test/java/com/exonum/client/TestUtils.java b/exonum-light-client/src/test/java/com/exonum/client/TestUtils.java index 431802d7f3..4b280b09f7 100644 --- a/exonum-light-client/src/test/java/com/exonum/client/TestUtils.java +++ b/exonum-light-client/src/test/java/com/exonum/client/TestUtils.java @@ -17,9 +17,13 @@ package com.exonum.client; import static com.exonum.binding.common.crypto.CryptoFunctions.ed25519; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; import com.exonum.binding.common.message.TransactionMessage; import com.google.common.io.BaseEncoding; +import okhttp3.mockwebserver.RecordedRequest; final class TestUtils { private static final BaseEncoding HEX_ENCODER = BaseEncoding.base16().lowerCase(); @@ -36,4 +40,11 @@ static String toHex(TransactionMessage message) { return HEX_ENCODER.encode(message.toBytes()); } + static void assertExactPath(RecordedRequest request, String url) { + assertThat(request.getPath(), is("/" + url)); + } + + static void assertPath(RecordedRequest request, String url) { + assertThat(request.getPath(), startsWith("/" + url)); + } }