diff --git a/b2/src/main/java/org/jclouds/b2/B2Api.java b/b2/src/main/java/org/jclouds/b2/B2Api.java index bbb64044d..bd4ca2867 100644 --- a/b2/src/main/java/org/jclouds/b2/B2Api.java +++ b/b2/src/main/java/org/jclouds/b2/B2Api.java @@ -20,6 +20,7 @@ import org.jclouds.b2.features.AuthorizationApi; import org.jclouds.b2.features.BucketApi; +import org.jclouds.b2.features.MultipartApi; import org.jclouds.b2.features.ObjectApi; import org.jclouds.rest.annotations.Delegate; @@ -33,4 +34,7 @@ public interface B2Api extends Closeable { @Delegate ObjectApi getObjectApi(); + + @Delegate + MultipartApi getMultipartApi(); } diff --git a/b2/src/main/java/org/jclouds/b2/binders/UploadPartBinder.java b/b2/src/main/java/org/jclouds/b2/binders/UploadPartBinder.java new file mode 100644 index 000000000..027541253 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/binders/UploadPartBinder.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.binders; + +import java.util.Map; + +import org.jclouds.http.HttpRequest; +import org.jclouds.b2.domain.GetUploadPartResponse; +import org.jclouds.rest.MapBinder; + +import com.google.common.net.HttpHeaders; + +public final class UploadPartBinder implements MapBinder { + @Override + public R bindToRequest(R request, Map postParams) { + GetUploadPartResponse uploadUrl = (GetUploadPartResponse) postParams.get("response"); + return (R) request.toBuilder() + .endpoint(uploadUrl.uploadUrl()) + .replaceHeader(HttpHeaders.AUTHORIZATION, uploadUrl.authorizationToken()) + .build(); + } + + @Override + public R bindToRequest(R request, Object input) { + throw new UnsupportedOperationException(); + } +} diff --git a/b2/src/main/java/org/jclouds/b2/domain/GetUploadPartResponse.java b/b2/src/main/java/org/jclouds/b2/domain/GetUploadPartResponse.java new file mode 100644 index 000000000..d6222cc05 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/domain/GetUploadPartResponse.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.net.URI; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class GetUploadPartResponse { + public abstract String fileId(); + public abstract URI uploadUrl(); + public abstract String authorizationToken(); + + @SerializedNames({"fileId", "uploadUrl", "authorizationToken"}) + public static GetUploadPartResponse create(String fileId, URI uploadUrl, String authorizationToken) { + return new AutoValue_GetUploadPartResponse(fileId, uploadUrl, authorizationToken); + } +} diff --git a/b2/src/main/java/org/jclouds/b2/domain/ListPartsResponse.java b/b2/src/main/java/org/jclouds/b2/domain/ListPartsResponse.java new file mode 100644 index 000000000..1ed9b4115 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/domain/ListPartsResponse.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; +import java.util.List; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; + +@AutoValue +public abstract class ListPartsResponse { + @Nullable public abstract Integer nextPartNumber(); + public abstract List parts(); + + @SerializedNames({"nextPartNumber", "parts"}) + public static ListPartsResponse create(@Nullable Integer nextPartNumber, List parts) { + return new AutoValue_ListPartsResponse(nextPartNumber, ImmutableList.copyOf(parts)); + } + + @AutoValue + public abstract static class Entry { + public abstract long contentLength(); + public abstract String contentSha1(); + public abstract String fileId(); + public abstract int partNumber(); + public abstract Date uploadTimestamp(); + + @SerializedNames({"contentLength", "contentSha1", "fileId", "partNumber", "uploadTimestamp"}) + public static Entry create(long contentLength, String contentSha1, String fileId, int partNumber, long uploadTimestamp) { + return new AutoValue_ListPartsResponse_Entry(contentLength, contentSha1, fileId, partNumber, new Date(uploadTimestamp)); + } + } +} diff --git a/b2/src/main/java/org/jclouds/b2/domain/ListUnfinishedLargeFilesResponse.java b/b2/src/main/java/org/jclouds/b2/domain/ListUnfinishedLargeFilesResponse.java new file mode 100644 index 000000000..986fd6387 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/domain/ListUnfinishedLargeFilesResponse.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +@AutoValue +public abstract class ListUnfinishedLargeFilesResponse { + @Nullable public abstract String nextFileId(); + public abstract List files(); + + @SerializedNames({"nextFileId", "files"}) + public static ListUnfinishedLargeFilesResponse create(@Nullable String nextFileId, List files) { + return new AutoValue_ListUnfinishedLargeFilesResponse(nextFileId, ImmutableList.copyOf(files)); + } + + @AutoValue + public abstract static class Entry { + public abstract String accountId(); + public abstract String bucketId(); + public abstract String contentType(); + public abstract String fileId(); + public abstract Map fileInfo(); + public abstract String fileName(); + public abstract Date uploadTimestamp(); + + @SerializedNames({"accountId", "bucketId", "contentType", "fileId", "fileInfo", "fileName", "uploadTimestamp"}) + public static Entry create(String accountId, String bucketId, String contentType, String fileId, Map fileInfo, String fileName, long uploadTimestamp) { + return new AutoValue_ListUnfinishedLargeFilesResponse_Entry(accountId, bucketId, contentType, fileId, ImmutableMap.copyOf(fileInfo), fileName, new Date(uploadTimestamp)); + } + } +} diff --git a/b2/src/main/java/org/jclouds/b2/domain/MultipartUploadResponse.java b/b2/src/main/java/org/jclouds/b2/domain/MultipartUploadResponse.java new file mode 100644 index 000000000..bfd1dfed1 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/domain/MultipartUploadResponse.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; +import java.util.Map; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; + +@AutoValue +public abstract class MultipartUploadResponse { + public abstract String accountId(); + public abstract String bucketId(); + public abstract String contentType(); + public abstract String fileId(); + public abstract Map fileInfo(); + public abstract String fileName(); + public abstract Date uploadTimestamp(); + + @SerializedNames({"accountId", "bucketId", "contentType", "fileId", "fileInfo", "fileName", "uploadTimestamp"}) + public static MultipartUploadResponse create(String accountId, String bucketId, String contentType, String fileId, Map fileInfo, String fileName, long uploadTimestamp) { + return new AutoValue_MultipartUploadResponse(accountId, bucketId, contentType, fileId, ImmutableMap.copyOf(fileInfo), fileName, new Date(uploadTimestamp)); + } +} diff --git a/b2/src/main/java/org/jclouds/b2/domain/UploadPartResponse.java b/b2/src/main/java/org/jclouds/b2/domain/UploadPartResponse.java new file mode 100644 index 000000000..1a8b3ad46 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/domain/UploadPartResponse.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class UploadPartResponse { + public abstract long contentLength(); + public abstract String contentSha1(); + public abstract String fileId(); + public abstract int partNumber(); + + @SerializedNames({"contentLength", "contentSha1", "fileId", "partNumber"}) + public static UploadPartResponse create(long contentLength, String contentSha1, String fileId, int partNumber) { + return new AutoValue_UploadPartResponse(contentLength, contentSha1, fileId, partNumber); + } +} diff --git a/b2/src/main/java/org/jclouds/b2/features/MultipartApi.java b/b2/src/main/java/org/jclouds/b2/features/MultipartApi.java new file mode 100644 index 000000000..5affc4e93 --- /dev/null +++ b/b2/src/main/java/org/jclouds/b2/features/MultipartApi.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER; + +import java.util.Collection; +import java.util.Map; + +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.jclouds.blobstore.attr.BlobScope; +import org.jclouds.io.Payload; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.b2.binders.UploadPartBinder; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.GetUploadPartResponse; +import org.jclouds.b2.domain.ListPartsResponse; +import org.jclouds.b2.domain.ListUnfinishedLargeFilesResponse; +import org.jclouds.b2.domain.MultipartUploadResponse; +import org.jclouds.b2.domain.UploadPartResponse; +import org.jclouds.b2.filters.RequestAuthorization; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.binders.BindToJsonPayload; + +@BlobScope(CONTAINER) +@Consumes(APPLICATION_JSON) +public interface MultipartApi { + @Named("b2_start_large_file") + @POST + @Path("/b2api/v1/b2_start_large_file") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + MultipartUploadResponse startLargeFile(@PayloadParam("bucketId") String bucketId, @PayloadParam("fileName") String fileName, @PayloadParam("contentType") String contentType, @PayloadParam("fileInfo") Map fileInfo); + + @Named("b2_cancel_large_file") + @POST + @Path("/b2api/v1/b2_cancel_large_file") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + B2Object cancelLargeFile(@PayloadParam("fileId") String fileId); + + @Named("b2_finish_large_file") + @POST + @Path("/b2api/v1/b2_finish_large_file") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + B2Object finishLargeFile(@PayloadParam("fileId") String fileId, @PayloadParam("partSha1Array") Collection contentSha1List); + + @Named("b2_get_upload_part_url") + @POST + @Path("/b2api/v1/b2_get_upload_part_url") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + GetUploadPartResponse getUploadPartUrl(@PayloadParam("fileId") String fileId); + + @Named("b2_upload_part") + @POST + @MapBinder(UploadPartBinder.class) + UploadPartResponse uploadPart(@PayloadParam("response") GetUploadPartResponse response, @HeaderParam("X-Bz-Part-Number") int partNumber, @HeaderParam("X-Bz-Content-Sha1") String sha1, @PayloadParam("payload") Payload payload); + + @Named("b2_list_parts") + @POST + @Path("/b2api/v1/b2_list_parts") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + ListPartsResponse listParts(@PayloadParam("fileId") String fileId, @PayloadParam("startPartNumber") @Nullable Integer startPartNumber, @PayloadParam("maxPartCount") @Nullable Integer maxPartCount); + + @Named("b2_list_unfinished_large_files") + @POST + @Path("/b2api/v1/b2_list_unfinished_large_files") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Produces(APPLICATION_JSON) + ListUnfinishedLargeFilesResponse listUnfinishedLargeFiles(@PayloadParam("bucketId") String bucketId, @PayloadParam("startFileId") @Nullable String startFileId, @PayloadParam("maxFileCount") @Nullable Integer maxFileCount); +} diff --git a/b2/src/test/java/org/jclouds/b2/features/B2TestUtils.java b/b2/src/test/java/org/jclouds/b2/features/B2TestUtils.java new file mode 100644 index 000000000..8d3e3a092 --- /dev/null +++ b/b2/src/test/java/org/jclouds/b2/features/B2TestUtils.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.net.URL; +import java.util.Set; +import java.util.Properties; + +import org.jclouds.ContextBuilder; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.b2.B2Api; +import org.jclouds.util.Strings2; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.inject.Module; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +final class B2TestUtils { + static B2Api api(String uri, String provider, Properties overrides) { + Set modules = ImmutableSet. of( + new ExecutorServiceModule(MoreExecutors.sameThreadExecutor())); + + return ContextBuilder.newBuilder(provider) + .credentials("ACCOUNT_ID", "APPLICATION_KEY") + .endpoint(uri) + .overrides(overrides) + .modules(modules) + .buildApi(B2Api.class); + } + + static B2Api api(String uri, String provider) { + return api(uri, provider, new Properties()); + } + + static MockWebServer createMockWebServer() throws IOException { + MockWebServer server = new MockWebServer(); + server.play(); + URL url = server.getUrl(""); + return server; + } + + static void assertAuthentication(MockWebServer server) { + assertThat(server.getRequestCount()).isGreaterThanOrEqualTo(1); + try { + assertThat(server.takeRequest().getRequestLine()).isEqualTo("GET /b2api/v1/b2_authorize_account HTTP/1.1"); + } catch (InterruptedException e) { + throw Throwables.propagate(e); + } + } + + /** + * Ensures the request has a json header for the proper REST methods. + * + * @param request + * @param method + * The request method (such as GET). + * @param path + * The path requested for this REST call. + * @see RecordedRequest + */ + static void assertRequest(RecordedRequest request, String method, String path) { + assertThat(request.getMethod()).isEqualTo(method); + assertThat(request.getPath()).isEqualTo(path); + } + + /** + * Ensures the request is json and has the same contents as the resource + * file provided. + * + * @param request + * @param method + * The request method (such as GET). + * @param resourceLocation + * The location of the resource file. Contents will be compared to + * the request body as JSON. + * @see RecordedRequest + */ + static void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) { + assertRequest(request, method, path); + assertContentTypeIsJson(request); + JsonParser parser = new JsonParser(); + JsonElement requestJson; + try { + requestJson = parser.parse(new String(request.getBody(), Charsets.UTF_8)); + } catch (Exception e) { + throw Throwables.propagate(e); + } + JsonElement resourceJson = parser.parse(stringFromResource(resourceLocation)); + assertThat(requestJson).isEqualTo(resourceJson); + } + + /** + * Ensures the request has a json header. + * + * @param request + * @see RecordedRequest + */ + private static void assertContentTypeIsJson(RecordedRequest request) { + assertThat(request.getHeaders()).contains("Content-Type: application/json"); + } + + /** + * Get a string from a resource + * + * @param resourceName + * The name of the resource. + * @return The content of the resource + */ + static String stringFromResource(String resourceName) { + try { + return Strings2.toStringAndClose(BucketApiMockTest.class.getResourceAsStream(resourceName)); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java b/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java index 09eb58a6f..5a6bf8544 100644 --- a/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java +++ b/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java @@ -17,37 +17,22 @@ package org.jclouds.b2.features; import static org.assertj.core.api.Assertions.assertThat; +import static org.jclouds.b2.features.B2TestUtils.api; +import static org.jclouds.b2.features.B2TestUtils.assertAuthentication; +import static org.jclouds.b2.features.B2TestUtils.assertRequest; +import static org.jclouds.b2.features.B2TestUtils.createMockWebServer; +import static org.jclouds.b2.features.B2TestUtils.stringFromResource; -import java.io.IOException; -import java.net.URL; -import java.util.Set; -import java.util.Properties; - -import org.jclouds.ContextBuilder; -import org.jclouds.concurrent.config.ExecutorServiceModule; -import org.jclouds.b2.B2Api; import org.jclouds.b2.domain.Bucket; import org.jclouds.b2.domain.BucketList; import org.jclouds.b2.domain.BucketType; -import org.jclouds.util.Strings2; import org.testng.annotations.Test; -import com.google.common.base.Charsets; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.inject.Module; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; @Test(groups = "unit", testName = "BucketApiMockTest") public final class BucketApiMockTest { - private final Set modules = ImmutableSet. of( - new ExecutorServiceModule(MoreExecutors.sameThreadExecutor())); - public void testCreateBucket() throws Exception { MockWebServer server = createMockWebServer(); server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); @@ -153,99 +138,4 @@ public void testListBuckets() throws Exception { server.shutdown(); } } - - public B2Api api(String uri, String provider, Properties overrides) { - return ContextBuilder.newBuilder(provider) - .credentials("ACCOUNT_ID", "APPLICATION_KEY") - .endpoint(uri) - .overrides(overrides) - .modules(modules) - .buildApi(B2Api.class); - } - - public B2Api api(String uri, String provider) { - return api(uri, provider, new Properties()); - } - - public static MockWebServer createMockWebServer() throws IOException { - MockWebServer server = new MockWebServer(); - server.play(); - URL url = server.getUrl(""); - return server; - } - - public void assertAuthentication(MockWebServer server) { - assertThat(server.getRequestCount()).isGreaterThanOrEqualTo(1); - try { - assertThat(server.takeRequest().getRequestLine()).isEqualTo("GET /b2api/v1/b2_authorize_account HTTP/1.1"); - } catch (InterruptedException e) { - throw Throwables.propagate(e); - } - } - - /** - * Ensures the request has a json header for the proper REST methods. - * - * @param request - * @param method - * The request method (such as GET). - * @param path - * The path requested for this REST call. - * @see RecordedRequest - */ - public void assertRequest(RecordedRequest request, String method, String path) { - assertThat(request.getMethod()).isEqualTo(method); - assertThat(request.getPath()).isEqualTo(path); - } - - /** - * Ensures the request is json and has the same contents as the resource - * file provided. - * - * @param request - * @param method - * The request method (such as GET). - * @param resourceLocation - * The location of the resource file. Contents will be compared to - * the request body as JSON. - * @see RecordedRequest - */ - public void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) { - assertRequest(request, method, path); - assertContentTypeIsJson(request); - JsonParser parser = new JsonParser(); - JsonElement requestJson; - try { - requestJson = parser.parse(new String(request.getBody(), Charsets.UTF_8)); - } catch (Exception e) { - throw Throwables.propagate(e); - } - JsonElement resourceJson = parser.parse(stringFromResource(resourceLocation)); - assertThat(requestJson).isEqualTo(resourceJson); - } - - /** - * Ensures the request has a json header. - * - * @param request - * @see RecordedRequest - */ - private void assertContentTypeIsJson(RecordedRequest request) { - assertThat(request.getHeaders()).contains("Content-Type: application/json"); - } - - /** - * Get a string from a resource - * - * @param resourceName - * The name of the resource. - * @return The content of the resource - */ - public String stringFromResource(String resourceName) { - try { - return Strings2.toStringAndClose(getClass().getResourceAsStream(resourceName)); - } catch (IOException e) { - throw Throwables.propagate(e); - } - } } diff --git a/b2/src/test/java/org/jclouds/b2/features/MultipartApiLiveTest.java b/b2/src/test/java/org/jclouds/b2/features/MultipartApiLiveTest.java new file mode 100644 index 000000000..332eee30f --- /dev/null +++ b/b2/src/test/java/org/jclouds/b2/features/MultipartApiLiveTest.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.Random; + +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.b2.domain.Action; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.Bucket; +import org.jclouds.b2.domain.BucketType; +import org.jclouds.b2.domain.GetUploadPartResponse; +import org.jclouds.b2.domain.ListPartsResponse; +import org.jclouds.b2.domain.ListUnfinishedLargeFilesResponse; +import org.jclouds.b2.domain.MultipartUploadResponse; +import org.jclouds.b2.internal.BaseB2ApiLiveTest; +import org.jclouds.utils.TestUtils; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteSource; + +public final class MultipartApiLiveTest extends BaseB2ApiLiveTest { + private static final Random random = new Random(); + + @Test(groups = "live") + public void testCancelMultipart() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + MultipartApi multipartApi = api.getMultipartApi(); + + String fileName = "file-name"; + String contentType = "text/plain"; + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket bucket = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + try { + MultipartUploadResponse response = multipartApi.startLargeFile(bucket.bucketId(), fileName, contentType, fileInfo); + multipartApi.cancelLargeFile(response.fileId()); + } finally { + bucketApi.deleteBucket(bucket.bucketId()); + } + } + + @Test(groups = "live") + public void testFinishMultipart() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + MultipartApi multipartApi = api.getMultipartApi(); + + String fileName = "file-name"; + String contentType = "text/plain"; + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket bucket = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + MultipartUploadResponse response = null; + B2Object b2Object = null; + try { + response = multipartApi.startLargeFile(bucket.bucketId(), fileName, contentType, fileInfo); + + ByteSource part1 = TestUtils.randomByteSource().slice(0, 100 * 1024 * 1024); + String hash1 = part1.hash(Hashing.sha1()).toString(); + Payload payload1 = Payloads.newByteSourcePayload(part1); + payload1.getContentMetadata().setContentLength(part1.size()); + GetUploadPartResponse uploadUrl = multipartApi.getUploadPartUrl(response.fileId()); + multipartApi.uploadPart(uploadUrl, 1, hash1, payload1); + + ByteSource part2 = TestUtils.randomByteSource().slice(0, 1); + String hash2 = part2.hash(Hashing.sha1()).toString(); + Payload payload2 = Payloads.newByteSourcePayload(part2); + payload2.getContentMetadata().setContentLength(part2.size()); + uploadUrl = multipartApi.getUploadPartUrl(response.fileId()); + multipartApi.uploadPart(uploadUrl, 2, hash2, payload2); + + b2Object = multipartApi.finishLargeFile(response.fileId(), ImmutableList.of(hash1, hash2)); + response = null; + + assertThat(b2Object.fileName()).isEqualTo(fileName); + assertThat(b2Object.fileInfo()).isEqualTo(fileInfo); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(b2Object.action()).isEqualTo(Action.UPLOAD); + assertThat(b2Object.bucketId()).isEqualTo(bucket.bucketId()); + assertThat(b2Object.contentLength()).isEqualTo(100 * 1024 * 1024 + 1); + assertThat(b2Object.contentType()).isEqualTo(contentType); + } finally { + if (b2Object != null) { + objectApi.deleteFileVersion(fileName, b2Object.fileId()); + } + if (response != null) { + multipartApi.cancelLargeFile(response.fileId()); + } + bucketApi.deleteBucket(bucket.bucketId()); + } + } + + @Test(groups = "live") + public void testListParts() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + MultipartApi multipartApi = api.getMultipartApi(); + + String fileName = "file-name"; + String contentType = "text/plain"; + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket bucket = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + MultipartUploadResponse response = null; + B2Object b2Object = null; + try { + response = multipartApi.startLargeFile(bucket.bucketId(), fileName, contentType, fileInfo); + + ListPartsResponse listParts = multipartApi.listParts(response.fileId(), 1, 1000); + assertThat(listParts.parts()).hasSize(0); + + long contentLength = 1024 * 1024; + ByteSource part = TestUtils.randomByteSource().slice(0, contentLength); + String hash = part.hash(Hashing.sha1()).toString(); + Payload payload = Payloads.newByteSourcePayload(part); + payload.getContentMetadata().setContentLength(contentLength); + GetUploadPartResponse uploadUrl = multipartApi.getUploadPartUrl(response.fileId()); + multipartApi.uploadPart(uploadUrl, 1, hash, payload); + + listParts = multipartApi.listParts(response.fileId(), 1, 1000); + assertThat(listParts.parts()).hasSize(1); + + ListPartsResponse.Entry entry = listParts.parts().get(0); + assertThat(entry.contentLength()).isEqualTo(contentLength); + assertThat(entry.contentSha1()).isEqualTo(hash); + assertThat(entry.partNumber()).isEqualTo(1); + } finally { + if (response != null) { + multipartApi.cancelLargeFile(response.fileId()); + } + bucketApi.deleteBucket(bucket.bucketId()); + } + } + + @Test(groups = "live") + public void testListUnfinishedLargeFiles() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + MultipartApi multipartApi = api.getMultipartApi(); + + String fileName = "file-name"; + String contentType = "text/plain"; + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket bucket = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + MultipartUploadResponse response = null; + B2Object b2Object = null; + try { + ListUnfinishedLargeFilesResponse unfinishedLargeFiles = multipartApi.listUnfinishedLargeFiles(bucket.bucketId(), null, null); + assertThat(unfinishedLargeFiles.files()).hasSize(0); + + response = multipartApi.startLargeFile(bucket.bucketId(), fileName, contentType, fileInfo); + + unfinishedLargeFiles = multipartApi.listUnfinishedLargeFiles(bucket.bucketId(), null, null); + assertThat(unfinishedLargeFiles.files()).hasSize(1); + + ListUnfinishedLargeFilesResponse.Entry entry = unfinishedLargeFiles.files().get(0); + assertThat(entry.contentType()).isEqualTo(contentType); + assertThat(entry.fileInfo()).isEqualTo(fileInfo); + assertThat(entry.fileName()).isEqualTo(fileName); + } finally { + if (response != null) { + multipartApi.cancelLargeFile(response.fileId()); + } + bucketApi.deleteBucket(bucket.bucketId()); + } + } + + private static String getBucketName() { + return "jcloudstestbucket-" + random.nextInt(Integer.MAX_VALUE); + } +} diff --git a/b2/src/test/java/org/jclouds/b2/features/MultipartApiMockTest.java b/b2/src/test/java/org/jclouds/b2/features/MultipartApiMockTest.java new file mode 100644 index 000000000..7220b88f4 --- /dev/null +++ b/b2/src/test/java/org/jclouds/b2/features/MultipartApiMockTest.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.jclouds.b2.features.B2TestUtils.api; +import static org.jclouds.b2.features.B2TestUtils.assertAuthentication; +import static org.jclouds.b2.features.B2TestUtils.assertRequest; +import static org.jclouds.b2.features.B2TestUtils.createMockWebServer; +import static org.jclouds.b2.features.B2TestUtils.stringFromResource; + +import java.net.URI; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.b2.domain.Action; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.GetUploadPartResponse; +import org.jclouds.b2.domain.ListPartsResponse; +import org.jclouds.b2.domain.ListUnfinishedLargeFilesResponse; +import org.jclouds.b2.domain.MultipartUploadResponse; +import org.jclouds.b2.domain.UploadPartResponse; +import org.jclouds.utils.TestUtils; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; + +@Test(groups = "unit", testName = "MultipartApiMockTest") +public final class MultipartApiMockTest { + private static final String ACCOUNT_ID = "YOUR_ACCOUNT_ID"; + private static final String AUTHORIZATION_TOKEN = "3_20160409004829_42b8f80ba60fb4323dcaad98_ec81302316fccc2260201cbf17813247f312cf3b_000_uplg"; + private static final String BUCKET_NAME = "BUCKET_NAME"; + private static final String BUCKET_ID = "e73ede9c9c8412db49f60715"; + private static final String CONTENT_TYPE = "b2/x-auto"; + private static final String FILE_ID = "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028"; + private static final Map FILE_INFO = ImmutableMap.of("author", "unknown"); + private static final String FILE_NAME = "bigfile.dat"; + private static final String SHA1 = "062685a84ab248d2488f02f6b01b948de2514ad8"; + private static final Date UPLOAD_TIMESTAMP = new Date(1460162909000L); + + public void testStartLargeFile() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/start_large_file_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + MultipartUploadResponse response = api.startLargeFile(BUCKET_ID, FILE_NAME, CONTENT_TYPE, FILE_INFO); + assertThat(response.accountId()).isEqualTo(ACCOUNT_ID); + assertThat(response.bucketId()).isEqualTo(BUCKET_ID); + assertThat(response.contentType()).isEqualTo(CONTENT_TYPE); + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.fileInfo()).isEqualTo(FILE_INFO); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + assertThat(response.uploadTimestamp()).isEqualTo(UPLOAD_TIMESTAMP); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_start_large_file", "/start_large_file_request.json"); + } finally { + server.shutdown(); + } + } + + public void testCancelLargeFile() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/cancel_large_file_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + B2Object response = api.cancelLargeFile(FILE_ID); + assertThat(response.accountId()).isEqualTo(ACCOUNT_ID); + assertThat(response.bucketId()).isEqualTo(BUCKET_ID); + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_cancel_large_file", "/cancel_large_file_request.json"); + } finally { + server.shutdown(); + } + } + + public void testFinishLargeFile() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/finish_large_file_response.json"))); + Collection sha1 = ImmutableList.of( + "0000000000000000000000000000000000000000", + "ffffffffffffffffffffffffffffffffffffffff"); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + B2Object response = api.finishLargeFile(FILE_ID, sha1); + assertThat(response.accountId()).isEqualTo(ACCOUNT_ID); + assertThat(response.action()).isEqualTo(Action.UPLOAD); + assertThat(response.bucketId()).isEqualTo(BUCKET_ID); + assertThat(response.contentLength()).isEqualTo(208158542); + assertThat(response.contentSha1()).isNull(); + assertThat(response.contentType()).isEqualTo(CONTENT_TYPE); + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.fileInfo()).isEqualTo(FILE_INFO); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + assertThat(response.uploadTimestamp()).isEqualTo(UPLOAD_TIMESTAMP); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_finish_large_file", "/finish_large_file_request.json"); + } finally { + server.shutdown(); + } + } + + public void testGetUploadPartUrl() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/get_upload_part_url_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + GetUploadPartResponse response = api.getUploadPartUrl(FILE_ID); + assertThat(response.authorizationToken()).isEqualTo(AUTHORIZATION_TOKEN); + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.uploadUrl()).isEqualTo(URI.create("https://pod-000-1016-09.backblaze.com/b2api/v1/b2_upload_part/4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001/0037")); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_get_upload_part_url", "/get_upload_part_url_request.json"); + } finally { + server.shutdown(); + } + } + + public void testUploadPart() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/upload_part_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + GetUploadPartResponse uploadPart = GetUploadPartResponse.create(FILE_ID, server.getUrl("/b2api/v1/b2_upload_part/4a48fe8875c6214145260818/c001_v0001007_t0042").toURI(), AUTHORIZATION_TOKEN); + long contentLength = 100 * 1000 * 1000; + Payload payload = Payloads.newByteSourcePayload(TestUtils.randomByteSource().slice(0, contentLength)); + payload.getContentMetadata().setContentLength(contentLength); + UploadPartResponse response = api.uploadPart(uploadPart, 1, SHA1, payload); + assertThat(response.contentLength()).isEqualTo(contentLength); + assertThat(response.contentSha1()).isEqualTo(SHA1); + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.partNumber()).isEqualTo(1); + + assertThat(server.getRequestCount()).isEqualTo(1); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_upload_part/4a48fe8875c6214145260818/c001_v0001007_t0042"); + } finally { + server.shutdown(); + } + } + + public void testListParts() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/list_parts_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + ListPartsResponse response = api.listParts(FILE_ID, 1, 1000); + assertThat(response.nextPartNumber()).isNull(); + assertThat(response.parts()).hasSize(3); + + ListPartsResponse.Entry entry = response.parts().get(0); + assertThat(entry.contentLength()).isEqualTo(100000000); + assertThat(entry.contentSha1()).isEqualTo("062685a84ab248d2488f02f6b01b948de2514ad8"); + assertThat(entry.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(entry.partNumber()).isEqualTo(1); + assertThat(entry.uploadTimestamp()).isEqualTo(new Date(1462212185000L)); + + entry = response.parts().get(1); + assertThat(entry.contentLength()).isEqualTo(100000000); + assertThat(entry.contentSha1()).isEqualTo("cf634751c3d9f6a15344f23cbf13f3fc9542addf"); + assertThat(entry.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(entry.partNumber()).isEqualTo(2); + assertThat(entry.uploadTimestamp()).isEqualTo(new Date(1462212296000L)); + + entry = response.parts().get(2); + assertThat(entry.contentLength()).isEqualTo(8158554); + assertThat(entry.contentSha1()).isEqualTo("00ad164147cbbd60aedb2b04ff66b0f74f962753"); + assertThat(entry.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(entry.partNumber()).isEqualTo(3); + assertThat(entry.uploadTimestamp()).isEqualTo(new Date(1462212327000L)); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_list_parts", "/list_parts_request.json"); + } finally { + server.shutdown(); + } + } + + public void testListUnfinishedLargeFiles() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/list_unfinished_large_files_response.json"))); + + try { + MultipartApi api = api(server.getUrl("/").toString(), "b2").getMultipartApi(); + ListUnfinishedLargeFilesResponse response = api.listUnfinishedLargeFiles(BUCKET_ID, FILE_ID, 1000); + assertThat(response.nextFileId()).isNull(); + assertThat(response.files()).hasSize(1); + + ListUnfinishedLargeFilesResponse.Entry entry = response.files().get(0); + assertThat(entry.accountId()).isEqualTo(ACCOUNT_ID); + assertThat(entry.bucketId()).isEqualTo(BUCKET_ID); + assertThat(entry.contentType()).isEqualTo("application/octet-stream"); + assertThat(entry.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(entry.fileInfo()).isEqualTo(FILE_INFO); + assertThat(entry.fileName()).isEqualTo(FILE_NAME); + assertThat(entry.uploadTimestamp()).isEqualTo(new Date(1462212184000L)); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_list_unfinished_large_files", "/list_unfinished_large_files_request.json"); + } finally { + server.shutdown(); + } + } +} diff --git a/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java b/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java index 100f909af..8524210d3 100644 --- a/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java +++ b/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java @@ -18,23 +18,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; +import static org.jclouds.b2.features.B2TestUtils.api; +import static org.jclouds.b2.features.B2TestUtils.assertAuthentication; +import static org.jclouds.b2.features.B2TestUtils.assertRequest; +import static org.jclouds.b2.features.B2TestUtils.createMockWebServer; +import static org.jclouds.b2.features.B2TestUtils.stringFromResource; -import java.io.IOException; import java.net.URI; -import java.net.URL; import java.util.Date; import java.util.Map; -import java.util.Set; -import java.util.Properties; -import org.jclouds.ContextBuilder; import org.jclouds.blobstore.ContainerNotFoundException; import org.jclouds.blobstore.KeyNotFoundException; -import org.jclouds.concurrent.config.ExecutorServiceModule; import org.jclouds.http.options.GetOptions; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; -import org.jclouds.b2.B2Api; import org.jclouds.b2.domain.Action; import org.jclouds.b2.domain.B2Object; import org.jclouds.b2.domain.B2ObjectList; @@ -43,28 +41,16 @@ import org.jclouds.b2.domain.UploadFileResponse; import org.jclouds.b2.domain.UploadUrlResponse; import org.jclouds.b2.reference.B2Headers; -import org.jclouds.util.Strings2; import org.testng.annotations.Test; -import com.google.common.base.Charsets; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; -import com.google.common.reflect.TypeToken; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.inject.Module; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.RecordedRequest; @Test(groups = "unit", testName = "ObjectApiMockTest") public final class ObjectApiMockTest { - private final Set modules = ImmutableSet. of( - new ExecutorServiceModule(MoreExecutors.sameThreadExecutor())); - private static final String BUCKET_NAME = "BUCKET_NAME"; private static final String BUCKET_ID = "4a48fe8875c6214145260818"; private static final String FILE_ID = "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104"; @@ -447,99 +433,4 @@ public void testHideFile() throws Exception { server.shutdown(); } } - - public B2Api api(String uri, String provider, Properties overrides) { - return ContextBuilder.newBuilder(provider) - .credentials("ACCOUNT_ID", "APPLICATION_KEY") - .endpoint(uri) - .overrides(overrides) - .modules(modules) - .buildApi(new TypeToken(getClass()) {}); - } - - public B2Api api(String uri, String provider) { - return api(uri, provider, new Properties()); - } - - public static MockWebServer createMockWebServer() throws IOException { - MockWebServer server = new MockWebServer(); - server.play(); - URL url = server.getUrl(""); - return server; - } - - public void assertAuthentication(MockWebServer server) { - assertThat(server.getRequestCount()).isGreaterThanOrEqualTo(1); - try { - assertThat(server.takeRequest().getRequestLine()).isEqualTo("GET /b2api/v1/b2_authorize_account HTTP/1.1"); - } catch (InterruptedException e) { - throw Throwables.propagate(e); - } - } - - /** - * Ensures the request has a json header for the proper REST methods. - * - * @param request - * @param method - * The request method (such as GET). - * @param path - * The path requested for this REST call. - * @see RecordedRequest - */ - public void assertRequest(RecordedRequest request, String method, String path) { - assertThat(request.getMethod()).isEqualTo(method); - assertThat(request.getPath()).isEqualTo(path); - } - - /** - * Ensures the request is json and has the same contents as the resource - * file provided. - * - * @param request - * @param method - * The request method (such as GET). - * @param resourceLocation - * The location of the resource file. Contents will be compared to - * the request body as JSON. - * @see RecordedRequest - */ - public void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) { - assertRequest(request, method, path); - assertContentTypeIsJson(request); - JsonParser parser = new JsonParser(); - JsonElement requestJson; - try { - requestJson = parser.parse(new String(request.getBody(), Charsets.UTF_8)); - } catch (Exception e) { - throw Throwables.propagate(e); - } - JsonElement resourceJson = parser.parse(stringFromResource(resourceLocation)); - assertThat(requestJson).isEqualTo(resourceJson); - } - - /** - * Ensures the request has a json header. - * - * @param request - * @see RecordedRequest - */ - private void assertContentTypeIsJson(RecordedRequest request) { - assertThat(request.getHeaders()).contains("Content-Type: application/json"); - } - - /** - * Get a string from a resource - * - * @param resourceName - * The name of the resource. - * @return The content of the resource - */ - public String stringFromResource(String resourceName) { - try { - return Strings2.toStringAndClose(getClass().getResourceAsStream(resourceName)); - } catch (IOException e) { - throw Throwables.propagate(e); - } - } } diff --git a/b2/src/test/resources/cancel_large_file_request.json b/b2/src/test/resources/cancel_large_file_request.json new file mode 100644 index 000000000..7dd68bdf6 --- /dev/null +++ b/b2/src/test/resources/cancel_large_file_request.json @@ -0,0 +1,3 @@ +{ + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028" +} diff --git a/b2/src/test/resources/cancel_large_file_response.json b/b2/src/test/resources/cancel_large_file_response.json new file mode 100644 index 000000000..84c6c0e26 --- /dev/null +++ b/b2/src/test/resources/cancel_large_file_response.json @@ -0,0 +1,6 @@ +{ + "accountId": "YOUR_ACCOUNT_ID", + "bucketId": "e73ede9c9c8412db49f60715", + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "fileName": "bigfile.dat" +} diff --git a/b2/src/test/resources/finish_large_file_request.json b/b2/src/test/resources/finish_large_file_request.json new file mode 100644 index 000000000..8cef10a9d --- /dev/null +++ b/b2/src/test/resources/finish_large_file_request.json @@ -0,0 +1,7 @@ +{ + "partSha1Array": [ + "0000000000000000000000000000000000000000", + "ffffffffffffffffffffffffffffffffffffffff" + ], + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028" +} diff --git a/b2/src/test/resources/finish_large_file_response.json b/b2/src/test/resources/finish_large_file_response.json new file mode 100644 index 000000000..b6632398b --- /dev/null +++ b/b2/src/test/resources/finish_large_file_response.json @@ -0,0 +1,14 @@ +{ + "accountId": "YOUR_ACCOUNT_ID", + "action": "upload", + "bucketId": "e73ede9c9c8412db49f60715", + "contentLength": 208158542, + "contentSha1": "none", + "contentType": "b2/x-auto", + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "fileInfo": { + "author": "unknown" + }, + "fileName": "bigfile.dat", + "uploadTimestamp": 1460162909000 +} diff --git a/b2/src/test/resources/get_upload_part_url_request.json b/b2/src/test/resources/get_upload_part_url_request.json new file mode 100644 index 000000000..7dd68bdf6 --- /dev/null +++ b/b2/src/test/resources/get_upload_part_url_request.json @@ -0,0 +1,3 @@ +{ + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028" +} diff --git a/b2/src/test/resources/get_upload_part_url_response.json b/b2/src/test/resources/get_upload_part_url_response.json new file mode 100644 index 000000000..7ebf04e68 --- /dev/null +++ b/b2/src/test/resources/get_upload_part_url_response.json @@ -0,0 +1,5 @@ +{ + "authorizationToken": "3_20160409004829_42b8f80ba60fb4323dcaad98_ec81302316fccc2260201cbf17813247f312cf3b_000_uplg", + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "uploadUrl": "https://pod-000-1016-09.backblaze.com/b2api/v1/b2_upload_part/4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001/0037" +} diff --git a/b2/src/test/resources/list_parts_request.json b/b2/src/test/resources/list_parts_request.json new file mode 100644 index 000000000..f998faa60 --- /dev/null +++ b/b2/src/test/resources/list_parts_request.json @@ -0,0 +1,5 @@ +{ + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "startPartNumber": 1, + "maxPartCount": 1000 +} diff --git a/b2/src/test/resources/list_parts_response.json b/b2/src/test/resources/list_parts_response.json new file mode 100644 index 000000000..6b4ccab0c --- /dev/null +++ b/b2/src/test/resources/list_parts_response.json @@ -0,0 +1,26 @@ +{ + "nextPartNumber": null, + "parts": [ + { + "contentLength": 100000000, + "contentSha1": "062685a84ab248d2488f02f6b01b948de2514ad8", + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001", + "partNumber": 1, + "uploadTimestamp": 1462212185000 + }, + { + "contentLength": 100000000, + "contentSha1": "cf634751c3d9f6a15344f23cbf13f3fc9542addf", + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001", + "partNumber": 2, + "uploadTimestamp": 1462212296000 + }, + { + "contentLength": 8158554, + "contentSha1": "00ad164147cbbd60aedb2b04ff66b0f74f962753", + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001", + "partNumber": 3, + "uploadTimestamp": 1462212327000 + } + ] +} diff --git a/b2/src/test/resources/list_unfinished_large_files_request.json b/b2/src/test/resources/list_unfinished_large_files_request.json new file mode 100644 index 000000000..e86d2a9f4 --- /dev/null +++ b/b2/src/test/resources/list_unfinished_large_files_request.json @@ -0,0 +1,5 @@ +{ + "bucketId": "e73ede9c9c8412db49f60715", + "startFileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "maxFileCount": 1000 +} diff --git a/b2/src/test/resources/list_unfinished_large_files_response.json b/b2/src/test/resources/list_unfinished_large_files_response.json new file mode 100644 index 000000000..753c589e3 --- /dev/null +++ b/b2/src/test/resources/list_unfinished_large_files_response.json @@ -0,0 +1,16 @@ +{ + "files": [ + { + "accountId": "YOUR_ACCOUNT_ID", + "bucketId": "e73ede9c9c8412db49f60715", + "contentType": "application/octet-stream", + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001", + "fileInfo": { + "author": "unknown" + }, + "fileName": "bigfile.dat", + "uploadTimestamp": 1462212184000 + } + ], + "nextFileId": null +} diff --git a/b2/src/test/resources/start_large_file_request.json b/b2/src/test/resources/start_large_file_request.json new file mode 100644 index 000000000..3b4e90f47 --- /dev/null +++ b/b2/src/test/resources/start_large_file_request.json @@ -0,0 +1,8 @@ +{ + "fileName": "bigfile.dat", + "bucketId": "e73ede9c9c8412db49f60715", + "contentType": "b2/x-auto", + "fileInfo": { + "author": "unknown" + } +} diff --git a/b2/src/test/resources/start_large_file_response.json b/b2/src/test/resources/start_large_file_response.json new file mode 100644 index 000000000..01838b47d --- /dev/null +++ b/b2/src/test/resources/start_large_file_response.json @@ -0,0 +1,11 @@ +{ + "accountId": "YOUR_ACCOUNT_ID", + "bucketId": "e73ede9c9c8412db49f60715", + "contentType": "b2/x-auto", + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "fileInfo": { + "author": "unknown" + }, + "fileName": "bigfile.dat", + "uploadTimestamp": 1460162909000 +} diff --git a/b2/src/test/resources/upload_part_response.json b/b2/src/test/resources/upload_part_response.json new file mode 100644 index 000000000..fb99aa428 --- /dev/null +++ b/b2/src/test/resources/upload_part_response.json @@ -0,0 +1,6 @@ +{ + "contentLength": 100000000, + "contentSha1": "062685a84ab248d2488f02f6b01b948de2514ad8", + "fileId": "4_za71f544e781e6891531b001a_f200ec353a2184825_d20160409_m004829_c000_v0001016_t0028", + "partNumber": 1 +}