diff --git a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java index 4f2953b3..b488cebc 100644 --- a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java +++ b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java @@ -93,7 +93,7 @@ public final class CloudStorageFileSystemProvider extends FileSystemProvider { private final @Nullable String userProject; // used only when we create a new instance of CloudStorageFileSystemProvider. - private static StorageOptions futureStorageOptions; + private static StorageOptions futureStorageOptions = StorageOptionsUtil.getDefaultInstance(); private static class LazyPathIterator extends AbstractIterator { private final Iterator blobIterator; @@ -198,20 +198,18 @@ public CloudStorageFileSystemProvider() { */ CloudStorageFileSystemProvider( @Nullable String userProject, @Nullable StorageOptions gcsStorageOptions) { - this.storageOptions = gcsStorageOptions; + this.storageOptions = + gcsStorageOptions != null + ? StorageOptionsUtil.mergeOptionsWithUserAgent(gcsStorageOptions) + : StorageOptionsUtil.getDefaultInstance(); this.userProject = userProject; } // Initialize this.storage, once. This may throw an exception if default authentication // credentials are not available (hence not doing it in the ctor). private void initStorage() { - if (this.storage != null) { - return; - } - if (storageOptions == null) { - this.storage = StorageOptions.getDefaultInstance().getService(); - } else { - this.storage = storageOptions.getService(); + if (this.storage == null) { + doInitStorage(); } } @@ -1037,4 +1035,9 @@ private IOException asIoException(StorageException oops) { } return new IOException(oops.getMessage(), oops); } + + @VisibleForTesting + void doInitStorage() { + this.storage = storageOptions.getService(); + } } diff --git a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java new file mode 100644 index 00000000..27c28bf3 --- /dev/null +++ b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java @@ -0,0 +1,119 @@ +/* + * Copyright 2021 Google LLC + * + * 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.google.cloud.storage.contrib.nio; + +import com.google.api.gax.rpc.FixedHeaderProvider; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.cloud.storage.StorageOptions; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +final class StorageOptionsUtil { + static final String USER_AGENT_ENTRY_NAME = "gcloud-java-nio"; + static final String USER_AGENT_ENTRY_VERSION = getVersion(); + private static final String USER_AGENT_ENTRY = + String.format("%s/%s", USER_AGENT_ENTRY_NAME, USER_AGENT_ENTRY_VERSION); + private static final FixedHeaderProvider DEFAULT_HEADER_PROVIDER = + FixedHeaderProvider.create("user-agent", USER_AGENT_ENTRY); + + private static final StorageOptions DEFAULT_STORAGE_OPTIONS_INSTANCE = + StorageOptions.newBuilder().setHeaderProvider(DEFAULT_HEADER_PROVIDER).build(); + private static final FixedHeaderProvider EMTPY_HEADER_PROVIDER = + FixedHeaderProvider.create(Collections.emptyMap()); + + private StorageOptionsUtil() {} + + static StorageOptions getDefaultInstance() { + return DEFAULT_STORAGE_OPTIONS_INSTANCE; + } + + static StorageOptions mergeOptionsWithUserAgent(StorageOptions providedStorageOptions) { + if (providedStorageOptions == DEFAULT_STORAGE_OPTIONS_INSTANCE) { + return providedStorageOptions; + } + + String userAgent = providedStorageOptions.getUserAgent(); + if (userAgent == null) { + return nullSafeSet(providedStorageOptions, DEFAULT_HEADER_PROVIDER); + } else { + if (!userAgent.contains(USER_AGENT_ENTRY_NAME)) { + HeaderProvider providedHeaderProvider = getHeaderProvider(providedStorageOptions); + Map newHeaders = new HashMap<>(providedHeaderProvider.getHeaders()); + newHeaders.put("user-agent", String.format("%s %s", userAgent, USER_AGENT_ENTRY)); + FixedHeaderProvider headerProvider = + FixedHeaderProvider.create(ImmutableMap.copyOf(newHeaders)); + return nullSafeSet(providedStorageOptions, headerProvider); + } else { + return providedStorageOptions; + } + } + } + + /** + * Due to some complex interactions between init and mocking, it's possible that the builder + * instance returned from {@link StorageOptions#toBuilder()} can be null. This utility method will + * attempt to create the builder and set the new header provider. If however the builder instance + * is null, the orignal options will be returned without setting the header provider. + * + *

Since this method is only every called by us trying to add our user-agent entry to the + * headers this makes our attempt effectively a no-op, which is much better than failing customer + * code. + */ + private static StorageOptions nullSafeSet( + StorageOptions storageOptions, HeaderProvider headerProvider) { + StorageOptions.Builder builder = storageOptions.toBuilder(); + if (builder == null) { + return storageOptions; + } else { + return builder.setHeaderProvider(headerProvider).build(); + } + } + + /** Resolve the version of google-cloud-nio for inclusion in request meta-data */ + private static String getVersion() { + // attempt to read the library's version from a properties file generated during the build + // this value should be read and cached for later use + String version = ""; + try (InputStream inputStream = + CloudStorageFileSystemProvider.class.getResourceAsStream( + "/META-INF/maven/com.google.cloud/google-cloud-nio/pom.properties")) { + if (inputStream != null) { + final Properties properties = new Properties(); + properties.load(inputStream); + version = properties.getProperty("version"); + } + } catch (IOException e) { + // ignore + } + return version; + } + + /** + * {@link com.google.cloud.ServiceOptions} does not specify a getter for the headerProvider, so + * instead merge with an empty provider. + */ + @VisibleForTesting + static HeaderProvider getHeaderProvider(StorageOptions options) { + return options.getMergedHeaderProvider(EMTPY_HEADER_PROVIDER); + } +} diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java index 5fb91542..fc673c0a 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java @@ -30,6 +30,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -52,6 +53,11 @@ public void before() { path = Paths.get(URI.create("gs://red/water")); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testReadAttributes() throws IOException { Files.write(path, HAPPY, CloudStorageOptions.withCacheControl("potato")); diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java index 9f0914dc..92efba8e 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java @@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -53,6 +54,11 @@ public void before() { dir = Paths.get(URI.create("gs://bucket/randompath/")); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testCacheControl() throws IOException { Files.write(path, HAPPY, CloudStorageOptions.withCacheControl("potato")); diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java index 2b901737..9f264ea0 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java @@ -26,6 +26,7 @@ import static java.nio.file.StandardOpenOption.CREATE_NEW; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; +import static org.junit.Assert.assertTrue; import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper; import com.google.cloud.testing.junit4.MultipleAttemptsRule; @@ -52,6 +53,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -99,6 +101,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions()); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testSize() throws Exception { Path path = Paths.get(URI.create("gs://bucket/wat")); @@ -795,6 +802,21 @@ public void testFromSpace() throws Exception { assertThat(path4.toString()).isEqualTo("/with/a%20percent"); } + @Test + public void testVersion_matchesAcceptablePatterns() { + String acceptableVersionPattern = "|(?:\\d+\\.\\d+\\.\\d+(?:-.*?)?(?:-SNAPSHOT)?)"; + String version = StorageOptionsUtil.USER_AGENT_ENTRY_VERSION; + assertTrue( + String.format("the loaded version '%s' did not match the acceptable pattern", version), + version.matches(acceptableVersionPattern)); + } + + @Test + public void getUserAgentStartsWithCorrectToken() { + assertThat(String.format("gcloud-java-nio/%s", StorageOptionsUtil.USER_AGENT_ENTRY_VERSION)) + .startsWith("gcloud-java-nio/"); + } + private static CloudStorageConfiguration permitEmptyPathComponents(boolean value) { return CloudStorageConfiguration.builder().permitEmptyPathComponents(value).build(); } diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java index 28198495..9914e8e1 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java @@ -46,6 +46,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -73,6 +74,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions()); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void checkDefaultOptions() throws IOException { // 1. We get the normal default if we don't do anything special. diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java index 16647fb0..e78d6859 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java @@ -29,6 +29,7 @@ import com.google.cloud.testing.junit4.MultipleAttemptsRule; import com.google.common.collect.Lists; import java.nio.file.Files; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -57,6 +58,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(mockOptions); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testIsDirectoryNoUserProject() { CloudStorageFileSystem fs = diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java index 8142a312..f0cecea0 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java @@ -16,56 +16,41 @@ package com.google.cloud.storage.contrib.nio; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.StorageOptions; import com.google.cloud.testing.junit4.MultipleAttemptsRule; import java.net.URI; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; /** Unit tests for {@link CloudStorageFileSystemProvider} late initialization. */ -@RunWith(JUnit4.class) +@RunWith(MockitoJUnitRunner.class) public class CloudStorageLateInitializationTest { @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); - private StorageOptions mockOptions; - - @Before - public void before() { - mockOptions = mock(StorageOptions.class); - Storage mockStorage = mock(Storage.class); - when(mockOptions.getService()).thenReturn(mockStorage); - CloudStorageFileSystemProvider.setStorageOptions(mockOptions); - } + @Spy private final CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider(); @Test public void ctorDoesNotCreateStorage() { - new CloudStorageFileSystemProvider(); - verify(mockOptions, never()).getService(); + verify(provider, never()).doInitStorage(); } @Test public void getPathCreatesStorageOnce() { - CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider(); provider.getPath(URI.create("gs://bucket1/wat")); provider.getPath(URI.create("gs://bucket2/wat")); - verify(mockOptions, times(1)).getService(); + verify(provider, times(1)).doInitStorage(); } @Test public void getFileSystemCreatesStorageOnce() { - CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider(); provider.getFileSystem(URI.create("gs://bucket1")); provider.getFileSystem(URI.create("gs://bucket2")); - verify(mockOptions, times(1)).getService(); + verify(provider, times(1)).doInitStorage(); } } diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java index adda3433..f541e903 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,6 +45,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions()); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testWithoutCaching() throws IOException { Path path = Paths.get(URI.create("gs://bucket/path")); diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java index 107569cc..2a2d47bb 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java @@ -30,6 +30,7 @@ import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -47,6 +48,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions()); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testCreate_neverRemoveExtraSlashes() throws IOException { try (CloudStorageFileSystem fs = CloudStorageFileSystem.forBucket("doodle")) { diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java index e6e97486..a202cc3f 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Arrays; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -61,6 +62,11 @@ public void before() { CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions()); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + @Test public void testInputStreamReads() throws IOException, InterruptedException { // fill in the file diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java index fdab9d7b..2c34bc02 100644 --- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java @@ -23,6 +23,7 @@ import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -45,6 +46,11 @@ public void setUp() { uriToCloudStorage = URI.create("gs://bucket/file.txt"); } + @After + public void after() { + CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance()); + } + /** We can create a Path object from a gs:// URI. * */ @Test public void testCreatePath() { diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java new file mode 100644 index 00000000..8dc93835 --- /dev/null +++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 Google LLC + * + * 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.google.cloud.storage.contrib.nio; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.api.gax.rpc.FixedHeaderProvider; +import com.google.api.gax.rpc.HeaderProvider; +import com.google.cloud.NoCredentials; +import com.google.cloud.storage.StorageOptions; +import java.util.Map; +import org.junit.Test; + +public final class StorageOptionsUtilTest { + + @Test + public void ensureDefaultInstanceCarriedThrough() { + StorageOptions defaultInstance = StorageOptionsUtil.getDefaultInstance(); + StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance); + + assertSame(defaultInstance, merged); + } + + @Test + public void ensureUserAgentEntryAddedIfUserAgentSpecified() { + StorageOptions defaultInstance = + defaults() + .toBuilder() + .setHeaderProvider(FixedHeaderProvider.create("user-agent", "asdf/ gcloud-java-nio/")) + .build(); + StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance); + + String mergedUserAgent = merged.getUserAgent(); + assertNotNull(mergedUserAgent); + assertTrue(mergedUserAgent.contains("gcloud-java-nio/")); + } + + @Test + public void ensureUserAgentEntryLeftInTactIfAlreadySpecifiesOurEntry() { + StorageOptions defaultInstance = defaults(); + StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance); + + String mergedUserAgent = merged.getUserAgent(); + assertNotNull(mergedUserAgent); + assertTrue(mergedUserAgent.contains("gcloud-java-nio/")); + } + + @Test + public void ensureAddingUserAgentEntryDoesNotClobberAnyProvidedHeaders() { + StorageOptions base = + defaults() + .toBuilder() + .setHeaderProvider( + FixedHeaderProvider.create( + "user-agent", "custom/", + "x-custom-header", "value")) + .build(); + StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(base); + + String mergedUserAgent = merged.getUserAgent(); + + HeaderProvider mergedHeaderProvider = StorageOptionsUtil.getHeaderProvider(merged); + Map headers = mergedHeaderProvider.getHeaders(); + String customHeader = headers.get("x-custom-header"); + + assertNotNull(mergedUserAgent); + assertNotNull(customHeader); + assertEquals("value", customHeader); + assertTrue(mergedUserAgent.contains("custom/ gcloud-java-nio/")); + } + + private StorageOptions defaults() { + return StorageOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setProjectId("proj") + .build(); + } +} diff --git a/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..1f0955d4 --- /dev/null +++ b/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline