Repository: googleapis/java-storage (java)

Issue:
Add delimiter BlobListOption 
Currently, it's possible to use '/' as a delimiter by using BlobListOption.currentDirectory()

That's a confusing name, let's add the delimiter option and direct users to use delimiter('/') instead.

Pull request description:
feat: add delimiter BlobListOption
Fixes #99

Patch:
diff --git a/.github/trusted-contribution.yml b/.github/trusted-contribution.yml
new file mode 100644
index 00000000..f247d5c7
--- /dev/null
+++ b/.github/trusted-contribution.yml
@@ -0,0 +1,2 @@
+trustedContributors:
+- renovate-bot
\ No newline at end of file
diff --git a/.kokoro/linkage-monitor.sh b/.kokoro/linkage-monitor.sh
index 1c0664ac..158641cb 100755
--- a/.kokoro/linkage-monitor.sh
+++ b/.kokoro/linkage-monitor.sh
@@ -23,7 +23,12 @@ cd github/java-storage/
 java -version
 echo ${JOB_TYPE}
 
-mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgcloud.download.skip=true -B -V
+mvn install -B -V \
+  -DskipTests=true \
+  -Dclirr.skip=true \
+  -Denforcer.skip=true \
+  -Dmaven.javadoc.skip=true \
+  -Dgcloud.download.skip=true
 
 # Kokoro job cloud-opensource-java/ubuntu/linkage-monitor-gcs creates this JAR
 JAR=linkage-monitor-latest-all-deps.jar
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 20c60263..29f04853 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -5,7 +5,7 @@
   <version>1.103.2-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <packaging>jar</packaging>
   <name>Google Cloud Storage</name>
-  <url>https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-clients/google-cloud-storage</url>
+  <url>https://github.com/googleapis/java-storage</url>
   <description>
     Java idiomatic client for Google Cloud Storage.
   </description>
@@ -34,7 +34,7 @@
     <dependency>
       <groupId>com.google.api-client</groupId>
       <artifactId>google-api-client</artifactId>
-      <version>1.30.7</version>
+      <version>1.30.8</version>
     </dependency>
     <dependency>
       <groupId>com.google.apis</groupId>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
index f26157d9..03630c61 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
@@ -169,7 +169,9 @@ public class SignatureInfo {
     canonicalRequest
         .append(serializer.serializeHeaderNames(canonicalizedExtensionHeaders))
         .append(COMPONENT_SEPARATOR);
-    canonicalRequest.append("UNSIGNED-PAYLOAD");
+
+    String userProvidedHash = canonicalizedExtensionHeaders.get("X-Goog-Content-SHA256");
+    canonicalRequest.append(userProvidedHash == null ? "UNSIGNED-PAYLOAD" : userProvidedHash);
 
     return Hashing.sha256()
         .hashString(canonicalRequest.toString(), StandardCharsets.UTF_8)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index 77c88e97..c7514b75 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -161,6 +161,21 @@ public interface Storage extends Service<StorageOptions> {
     }
   }
 
+  enum UriScheme {
+    HTTP("http"),
+    HTTPS("https");
+
+    private final String scheme;
+
+    UriScheme(String scheme) {
+      this.scheme = scheme;
+    }
+
+    public String getScheme() {
+      return scheme;
+    }
+  }
+
   /** Class for specifying bucket target options. */
   class BucketTargetOption extends Option {
 
@@ -988,6 +1003,16 @@ public interface Storage extends Service<StorageOptions> {
       return new BlobListOption(StorageRpc.Option.DELIMITER, true);
     }
 
+    /**
+     * Returns an option to set a delimiter.
+     *
+     * @param delimiter generally '/' is the one used most often, but you can used other delimiters
+     *     as well.
+     */
+    public static BlobListOption delimiter(String delimiter) {
+      return new BlobListOption(StorageRpc.Option.DELIMITER, delimiter);
+    }
+
     /**
      * Returns an option to define the billing user project. This option is required by buckets with
      * `requester_pays` flag enabled to assign operation costs.
@@ -1038,6 +1063,7 @@ public interface Storage extends Service<StorageOptions> {
       HOST_NAME,
       PATH_STYLE,
       VIRTUAL_HOSTED_STYLE,
+      BUCKET_BOUND_HOST_NAME,
       QUERY_PARAMS
     }
 
@@ -1160,6 +1186,44 @@ public interface Storage extends Service<StorageOptions> {
       return new SignUrlOption(Option.PATH_STYLE, "");
     }
 
+    /**
+     * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+     * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+     * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+     * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This
+     * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code
+     * withBucketBoundHostname("...", UriScheme.HTTP).}
+     *
+     * @see <a href="https://cloud.google.com/storage/docs/request-endpoints#cname">CNAME
+     *     Redirects</a>
+     * @see <a
+     *     href="https://cloud.google.com/load-balancing/docs/https/adding-backend-buckets-to-load-balancers">
+     *     GCLB Redirects</a>
+     */
+    public static SignUrlOption withBucketBoundHostname(String bucketBoundHostname) {
+      return withBucketBoundHostname(bucketBoundHostname, UriScheme.HTTP);
+    }
+
+    /**
+     * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+     * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+     * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+     * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. The
+     * bucket name itself should not include the URI scheme (http or https), so it is specified via
+     * a local enum.
+     *
+     * @see <a href="https://cloud.google.com/storage/docs/request-endpoints#cname">CNAME
+     *     Redirects</a>
+     * @see <a
+     *     href="https://cloud.google.com/load-balancing/docs/https/adding-backend-buckets-to-load-balancers">
+     *     GCLB Redirects</a>
+     */
+    public static SignUrlOption withBucketBoundHostname(
+        String bucketBoundHostname, UriScheme uriScheme) {
+      return new SignUrlOption(
+          Option.BUCKET_BOUND_HOST_NAME, uriScheme.getScheme() + "://" + bucketBoundHostname);
+    }
+
     /**
      * Use if the URL should contain additional query parameters.
      *
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index 63c19855..0f38e382 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -660,8 +660,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
     checkArgument(
         !(optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)
-            && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)),
-        "Cannot specify both the VIRTUAL_HOSTED_STYLE and PATH_STYLE SignUrlOptions together.");
+            && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)
+            && optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)),
+        "Only one of VIRTUAL_HOSTED_STYLE, PATH_STYLE, or BUCKET_BOUND_HOST_NAME SignUrlOptions can be"
+            + " specified.");
 
     String bucketName = slashlessBucketNameFromBlobInfo(blobInfo);
     String escapedBlobName = "";
@@ -676,6 +678,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
             ? STORAGE_XML_URI_SCHEME + "://" + getBaseStorageHostName(optionMap)
             : STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap);
 
+    if (optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
+      storageXmlHostName = (String) optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME);
+    }
+
     String stPath =
         usePathStyle
             ? constructResourceUriPath(bucketName, escapedBlobName, optionMap)
@@ -753,9 +759,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       }
       return pathBuilder.toString();
     }
-    if (!escapedBlobName.startsWith(PATH_DELIMITER)) {
-      pathBuilder.append(PATH_DELIMITER);
-    }
+    pathBuilder.append(PATH_DELIMITER);
     pathBuilder.append(escapedBlobName);
     return pathBuilder.toString();
   }
@@ -776,7 +780,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   private boolean shouldUsePathStyleForSignedUrl(EnumMap<SignUrlOption.Option, Object> optionMap) {
     // TODO(#6362): If we decide to change the default style used to generate URLs, switch this
     // logic to return false unless PATH_STYLE was explicitly specified.
-    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) {
+    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)
+        || optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
       return false;
     }
     return true;
@@ -836,7 +841,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         extHeadersBuilder.put(
             "host",
             slashlessBucketNameFromBlobInfo(blobInfo) + "." + getBaseStorageHostName(optionMap));
-      } else if (optionMap.containsKey(SignUrlOption.Option.HOST_NAME)) {
+      } else if (optionMap.containsKey(SignUrlOption.Option.HOST_NAME)
+          || optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
         extHeadersBuilder.put("host", getBaseStorageHostName(optionMap));
       }
     }
@@ -868,9 +874,14 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   /** Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". */
   private String getBaseStorageHostName(Map<SignUrlOption.Option, Object> optionMap) {
     String specifiedBaseHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME);
+    String bucketBoundHostName =
+        (String) optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME);
     if (!Strings.isNullOrEmpty(specifiedBaseHostName)) {
       return specifiedBaseHostName.replaceFirst("http(s)?://", "");
     }
+    if (!Strings.isNullOrEmpty(bucketBoundHostName)) {
+      return bucketBoundHostName.replaceFirst("http(s)?://", "");
+    }
     return STORAGE_XML_URI_HOST_NAME;
   }
 
@@ -1583,9 +1594,11 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       Object prev = temp.put(option.getRpcOption(), option.getValue());
       checkArgument(prev == null, "Duplicate option %s", option);
     }
-    Boolean value = (Boolean) temp.remove(DELIMITER);
-    if (Boolean.TRUE.equals(value)) {
+    if (Boolean.TRUE.equals(temp.get(DELIMITER))) {
+      temp.remove(DELIMITER);
       temp.put(DELIMITER, PATH_DELIMITER);
+    } else if (null != temp.get(DELIMITER)) {
+      temp.put(DELIMITER, temp.get(DELIMITER));
     }
     if (useAsSource) {
       addToOptionMap(IF_GENERATION_MATCH, IF_SOURCE_GENERATION_MATCH, generation, temp);
diff --git a/pom.xml b/pom.xml
index 4663b72c..18578b2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,21 +103,21 @@
       <dependency>
         <groupId>io.grpc</groupId>
         <artifactId>grpc-bom</artifactId>
-        <version>1.26.0</version>
+        <version>1.27.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <dependency>
         <groupId>com.google.api</groupId>
         <artifactId>gax-bom</artifactId>
-        <version>1.53.0</version>
+        <version>1.53.1</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <dependency>
         <groupId>com.google.protobuf</groupId>
         <artifactId>protobuf-bom</artifactId>
-        <version>3.11.1</version>
+        <version>3.11.3</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -212,7 +212,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-conformance-tests</artifactId>
-        <version>0.0.4</version>
+        <version>0.0.5</version>
         <scope>test</scope>
       </dependency>
     </dependencies>
@@ -225,7 +225,10 @@
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-dependency-plugin</artifactId>
           <configuration>
-            <ignoredUnusedDeclaredDependencies>org.objenesis:objenesis</ignoredUnusedDeclaredDependencies>
+            <ignoredUnusedDeclaredDependencies>
+              <ignoredUnusedDeclaredDependency>org.hamcrest:hamcrest</ignoredUnusedDeclaredDependency>
+              <ignoredUnusedDeclaredDependency>org.objenesis:objenesis</ignoredUnusedDeclaredDependency>
+            </ignoredUnusedDeclaredDependencies>
           </configuration>
         </plugin>
       </plugins>
diff --git a/renovate.json b/renovate.json
index a5cdff45..268a4669 100644
--- a/renovate.json
+++ b/renovate.json
@@ -22,7 +22,8 @@
         "^com.google.api:gax",
         "^com.google.auth:",
         "^com.google.cloud:google-cloud-core",
-        "^io.grpc:"
+        "^io.grpc:",
+        "^com.google.guava:"
       ],
       "groupName": "core dependencies"
     },
diff --git a/synth.metadata b/synth.metadata
index 36429ca6..cb135d8a 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -1,5 +1,5 @@
 {
-  "updateTime": "2020-01-16T09:02:51.520248Z",
+  "updateTime": "2020-02-01T09:02:33.355460Z",
   "sources": [
     {
       "template": {
@@ -8,421 +8,5 @@
         "version": "2019.10.17"
       }
     }
-  ],
-  "newFiles": [
-    {
-      "path": "pom.xml"
-    },
-    {
-      "path": ".repo-metadata.json"
-    },
-    {
-      "path": "java.header"
-    },
-    {
-      "path": "CONTRIBUTING.md"
-    },
-    {
-      "path": "synth.metadata"
-    },
-    {
-      "path": "renovate.json"
-    },
-    {
-      "path": "versions.txt"
-    },
-    {
-      "path": "synth.py"
-    },
-    {
-      "path": "license-checks.xml"
-    },
-    {
-      "path": "CHANGELOG.md"
-    },
-    {
-      "path": "CODE_OF_CONDUCT.md"
-    },
-    {
-      "path": "LICENSE"
-    },
-    {
-      "path": "codecov.yaml"
-    },
-    {
-      "path": ".gitignore"
-    },
-    {
-      "path": "README.md"
-    },
-    {
-      "path": ".github/release-please.yml"
-    },
-    {
-      "path": ".github/PULL_REQUEST_TEMPLATE.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/feature_request.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/support_request.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/bug_report.md"
-    },
-    {
-      "path": ".kokoro/build.sh"
-    },
-    {
-      "path": ".kokoro/common.cfg"
-    },
-    {
-      "path": ".kokoro/build.bat"
-    },
-    {
-      "path": ".kokoro/linkage-monitor.sh"
-    },
-    {
-      "path": ".kokoro/coerce_logs.sh"
-    },
-    {
-      "path": ".kokoro/dependencies.sh"
-    },
-    {
-      "path": ".kokoro/trampoline.sh"
-    },
-    {
-      "path": ".kokoro/continuous/samples.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/integration.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java11.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/propose_release.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/common.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/propose_release.sh"
-    },
-    {
-      "path": ".kokoro/continuous/java7.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/lint.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8-win.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/samples.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/integration.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java11.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/common.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java7.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/lint.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8-win.cfg"
-    },
-    {
-      "path": ".kokoro/release/publish_javadoc.cfg"
-    },
-    {
-      "path": ".kokoro/release/common.sh"
-    },
-    {
-      "path": ".kokoro/release/promote.sh"
-    },
-    {
-      "path": ".kokoro/release/common.cfg"
-    },
-    {
-      "path": ".kokoro/release/publish_javadoc.sh"
-    },
-    {
-      "path": ".kokoro/release/drop.sh"
-    },
-    {
-      "path": ".kokoro/release/stage.sh"
-    },
-    {
-      "path": ".kokoro/release/promote.cfg"
-    },
-    {
-      "path": ".kokoro/release/snapshot.cfg"
-    },
-    {
-      "path": ".kokoro/release/bump_snapshot.cfg"
-    },
-    {
-      "path": ".kokoro/release/drop.cfg"
-    },
-    {
-      "path": ".kokoro/release/bump_snapshot.sh"
-    },
-    {
-      "path": ".kokoro/release/stage.cfg"
-    },
-    {
-      "path": ".kokoro/release/snapshot.sh"
-    },
-    {
-      "path": ".kokoro/presubmit/samples.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/integration.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java11.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/linkage-monitor.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/common.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java7.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/lint.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/clirr.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8-win.cfg"
-    },
-    {
-      "path": "google-cloud-storage/pom.xml"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchResultTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/OptionTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CorsTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageExceptionTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CopyRequestTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageRoles.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/package-info.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageFactory.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/HttpMethod.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatchResult.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/SignedUrlEncodingHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/StorageRpcFactory.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/RpcBatch.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java"
-    }
   ]
 }
\ No newline at end of file


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
index 34f2478d..d13cc74a 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
@@ -1166,6 +1166,23 @@ public class StorageImplTest {
     assertArrayEquals(blobList.toArray(), Iterables.toArray(page.getValues(), Blob.class));
   }
 
+  @Test
+  public void testListBlobsDelimiter() {
+    String cursor = "cursor";
+    String delimiter = "/";
+    Map<StorageRpc.Option, ?> options = ImmutableMap.of(StorageRpc.Option.DELIMITER, delimiter);
+    ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+    EasyMock.expect(storageRpcMock.list(BUCKET_NAME1, options)).andReturn(result);
+    EasyMock.replay(storageRpcMock);
+    initializeService();
+    ImmutableList<Blob> blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page<Blob> page = storage.list(BUCKET_NAME1, Storage.BlobListOption.delimiter(delimiter));
+    assertEquals(cursor, page.getNextPageToken());
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.getValues(), Blob.class));
+  }
+
   @Test
   public void testUpdateBucket() {
     BucketInfo updatedBucketInfo = BUCKET_INFO1.toBuilder().setIndexPage("some-page").build();
@@ -1808,6 +1825,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://storage.googleapis.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(expectedResourcePath)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -1825,6 +1843,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(expectedResourcePath);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -1857,6 +1876,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://example.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -1874,6 +1894,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2019,6 +2040,7 @@ public class StorageImplTest {
       String expectedUrl =
           new StringBuilder("https://storage.googleapis.com/")
               .append(BUCKET_NAME1)
+              .append("/")
               .append(expectedBlobName)
               .append("?GoogleAccessId=")
               .append(ACCOUNT)
@@ -2036,6 +2058,7 @@ public class StorageImplTest {
           .append(42L + 1209600)
           .append("\n/")
           .append(BUCKET_NAME1)
+          .append("/")
           .append(expectedBlobName);
 
       Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2075,6 +2098,7 @@ public class StorageImplTest {
       String expectedUrl =
           new StringBuilder("https://example.com/")
               .append(BUCKET_NAME1)
+              .append("/")
               .append(expectedBlobName)
               .append("?GoogleAccessId=")
               .append(ACCOUNT)
@@ -2092,6 +2116,7 @@ public class StorageImplTest {
           .append(42L + 1209600)
           .append("\n/")
           .append(BUCKET_NAME1)
+          .append("/")
           .append(expectedBlobName);
 
       Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2243,6 +2268,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://storage.googleapis.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -2260,6 +2286,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2293,6 +2320,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://example.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -2310,6 +2338,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
index 8e1ad310..d45cb6dc 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
@@ -16,11 +16,8 @@
 
 package com.google.cloud.storage;
 
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeThat;
 
 import com.google.api.core.ApiClock;
 import com.google.auth.oauth2.ServiceAccountCredentials;
@@ -94,11 +91,6 @@ public class V4SigningTest {
 
   @Test
   public void test() {
-    assumeThat(
-        "Test skipped until b/136171758 is resolved",
-        testName.getMethodName(),
-        is(not("test[Headers should be trimmed]")));
-
     Storage storage =
         RemoteStorageHelper.create()
             .getOptions()
@@ -110,6 +102,19 @@ public class V4SigningTest {
 
     BlobInfo blob = BlobInfo.newBuilder(testData.getBucket(), testData.getObject()).build();
 
+    SignUrlOption style = SignUrlOption.withPathStyle();
+
+    if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.VIRTUAL_HOSTED_STYLE)) {
+      style = SignUrlOption.withVirtualHostedStyle();
+    } else if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.PATH_STYLE)) {
+      style = SignUrlOption.withPathStyle();
+    } else if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.BUCKET_BOUND_DOMAIN)) {
+      style =
+          SignUrlOption.withBucketBoundHostname(
+              testData.getBucketBoundDomain(),
+              Storage.UriScheme.valueOf(testData.getScheme().toUpperCase()));
+    }
+
     final String signedUrl =
         storage
             .signUrl(
@@ -118,7 +123,9 @@ public class V4SigningTest {
                 TimeUnit.SECONDS,
                 SignUrlOption.httpMethod(HttpMethod.valueOf(testData.getMethod())),
                 SignUrlOption.withExtHeaders(testData.getHeadersMap()),
-                SignUrlOption.withV4Signature())
+                SignUrlOption.withV4Signature(),
+                SignUrlOption.withQueryParams(testData.getQueryParametersMap()),
+                style)
             .toString();
     assertEquals(testData.getExpectedUrl(), signedUrl);
   }