Skip to content

Commit 9aceff9

Browse files
committed
test(showcase): add integration tests for resumable upload
1 parent ec6d4bf commit 9aceff9

2 files changed

Lines changed: 361 additions & 0 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.showcase.v1beta1.it;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
22+
import com.google.api.gax.core.FixedCredentialsProvider;
23+
import com.google.api.gax.core.NoCredentialsProvider;
24+
import com.google.api.gax.rpc.FailedPreconditionException;
25+
import com.google.api.gax.rpc.ResumableUploadCallSettings;
26+
import com.google.api.gax.rpc.ResumableUploadFuture;
27+
import com.google.auth.Credentials;
28+
import com.google.auth.oauth2.AccessToken;
29+
import com.google.auth.oauth2.OAuth2Credentials;
30+
import com.google.showcase.v1beta1.ResumableUploadServiceClient;
31+
import com.google.showcase.v1beta1.UploadMediaRequest;
32+
import com.google.showcase.v1beta1.UploadMediaResponse;
33+
import com.google.showcase.v1beta1.it.util.TestClientInitializer;
34+
import java.io.ByteArrayInputStream;
35+
import java.io.IOException;
36+
import java.io.InputStream;
37+
import java.nio.charset.StandardCharsets;
38+
import java.nio.file.Files;
39+
import java.nio.file.Path;
40+
import java.util.Date;
41+
import java.util.concurrent.TimeUnit;
42+
import org.junit.jupiter.api.AfterAll;
43+
import org.junit.jupiter.api.BeforeAll;
44+
import org.junit.jupiter.api.Test;
45+
import org.junit.jupiter.api.io.TempDir;
46+
47+
/**
48+
* Integration tests for generated {@link ResumableUploadServiceClient} against the Showcase server.
49+
*/
50+
class ITResumableUpload {
51+
52+
private static final int SHOWCASE_CHUNK_SIZE = 256 * 1024; // 256KB
53+
private static final Credentials DUMMY_CREDENTIALS =
54+
OAuth2Credentials.create(new AccessToken("fake-token", new Date(Long.MAX_VALUE)));
55+
private static ResumableUploadServiceClient client;
56+
57+
@BeforeAll
58+
static void createClients() throws Exception {
59+
client = TestClientInitializer.createHttpJsonResumableUploadClient(SHOWCASE_CHUNK_SIZE);
60+
}
61+
62+
@AfterAll
63+
static void destroyClients() throws InterruptedException {
64+
if (client != null) {
65+
client.close();
66+
client.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
67+
}
68+
}
69+
70+
@Test
71+
void testGeneratedClient_uploadMedia_synchronousConvenienceMethod(@TempDir Path tempDir)
72+
throws Exception {
73+
Path file =
74+
createTempFile(
75+
tempDir,
76+
"it-client-sync.txt",
77+
"Hello from generated ResumableUploadServiceClient synchronous convenience method!"
78+
.getBytes(StandardCharsets.UTF_8));
79+
UploadMediaRequest request =
80+
UploadMediaRequest.newBuilder().setName("it-client-sync.txt").build();
81+
82+
try (InputStream stream = Files.newInputStream(file)) {
83+
UploadMediaResponse response = client.uploadMedia(request, stream);
84+
assertThat(response.getName()).isEqualTo("it-client-sync.txt");
85+
assertThat(response.getSize()).isEqualTo(Files.size(file));
86+
}
87+
}
88+
89+
@Test
90+
void testGeneratedClient_uploadMediaCallable_asynchronousFutureCall(@TempDir Path tempDir)
91+
throws Exception {
92+
Path file =
93+
createTempFile(
94+
tempDir,
95+
"it-client-callable.txt",
96+
"Hello from generated ResumableUploadServiceClient callable futureCall!"
97+
.getBytes(StandardCharsets.UTF_8));
98+
UploadMediaRequest request =
99+
UploadMediaRequest.newBuilder().setName("it-client-callable.txt").build();
100+
101+
try (InputStream stream = Files.newInputStream(file)) {
102+
ResumableUploadFuture<UploadMediaResponse> future =
103+
client
104+
.uploadMediaCallable()
105+
.futureCall(request, stream, (ResumableUploadCallSettings) null);
106+
107+
UploadMediaResponse response = future.get(10, TimeUnit.SECONDS);
108+
assertThat(future.isDone()).isTrue();
109+
assertThat(future.isCancelled()).isFalse();
110+
assertThat(future.getUploadSessionUrl()).isNotNull();
111+
assertThat(future.getUploadSessionUrl()).contains("/resumable/upload");
112+
assertThat(response.getName()).isEqualTo("it-client-callable.txt");
113+
assertThat(response.getSize()).isEqualTo(Files.size(file));
114+
}
115+
}
116+
117+
@Test
118+
void testGeneratedClient_multiChunkUpload(@TempDir Path tempDir) throws Exception {
119+
// 600KB payload = 2 full 256KB chunks + 1 partial 88KB chunk
120+
int totalBytes = 600 * 1024;
121+
Path file = createTempFile(tempDir, "it-client-multi-chunk.txt", totalBytes);
122+
UploadMediaRequest request =
123+
UploadMediaRequest.newBuilder().setName("it-client-multi-chunk.txt").build();
124+
125+
try (InputStream stream = Files.newInputStream(file)) {
126+
UploadMediaResponse response = client.uploadMedia(request, stream);
127+
assertThat(response.getName()).isEqualTo("it-client-multi-chunk.txt");
128+
assertThat(response.getSize()).isEqualTo(Files.size(file));
129+
}
130+
}
131+
132+
@Test
133+
void testGeneratedClient_zeroByteUpload() throws Exception {
134+
UploadMediaRequest request =
135+
UploadMediaRequest.newBuilder().setName("it-client-zero-byte.txt").build();
136+
137+
try (InputStream stream = new ByteArrayInputStream(new byte[0])) {
138+
UploadMediaResponse response = client.uploadMedia(request, stream);
139+
assertThat(response.getName()).isEqualTo("it-client-zero-byte.txt");
140+
assertThat(response.getSize()).isEqualTo(0);
141+
}
142+
}
143+
144+
@Test
145+
void testGeneratedClient_exactChunkBoundaryUpload(@TempDir Path tempDir) throws Exception {
146+
// Exactly 2 full 256KB chunks (512KB total) -> triggers 0-byte finalize request
147+
int totalBytes = 512 * 1024;
148+
Path file = createTempFile(tempDir, "it-client-exact-chunks.txt", totalBytes);
149+
UploadMediaRequest request =
150+
UploadMediaRequest.newBuilder().setName("it-client-exact-chunks.txt").build();
151+
152+
try (InputStream stream = Files.newInputStream(file)) {
153+
UploadMediaResponse response = client.uploadMedia(request, stream);
154+
assertThat(response.getName()).isEqualTo("it-client-exact-chunks.txt");
155+
assertThat(response.getSize()).isEqualTo(Files.size(file));
156+
}
157+
}
158+
159+
@Test
160+
void testGeneratedClient_grpcClientDelegation_uploadMedia(@TempDir Path tempDir)
161+
throws Exception {
162+
Path file =
163+
createTempFile(
164+
tempDir,
165+
"it-grpc-delegation.txt",
166+
"Hello from generated ResumableUploadServiceClient gRPC delegation!"
167+
.getBytes(StandardCharsets.UTF_8));
168+
UploadMediaRequest request =
169+
UploadMediaRequest.newBuilder().setName("it-grpc-delegation.txt").build();
170+
171+
try (ResumableUploadServiceClient grpcClient =
172+
TestClientInitializer.createGrpcResumableUploadClient(
173+
FixedCredentialsProvider.create(DUMMY_CREDENTIALS), SHOWCASE_CHUNK_SIZE)) {
174+
try (InputStream stream = Files.newInputStream(file)) {
175+
UploadMediaResponse response = grpcClient.uploadMedia(request, stream);
176+
assertThat(response.getName()).isEqualTo("it-grpc-delegation.txt");
177+
assertThat(response.getSize()).isEqualTo(Files.size(file));
178+
}
179+
180+
try (InputStream stream = Files.newInputStream(file)) {
181+
ResumableUploadFuture<UploadMediaResponse> future =
182+
grpcClient
183+
.uploadMediaCallable()
184+
.futureCall(request, stream, (ResumableUploadCallSettings) null);
185+
UploadMediaResponse response = future.get(10, TimeUnit.SECONDS);
186+
assertThat(future.isDone()).isTrue();
187+
assertThat(future.isCancelled()).isFalse();
188+
assertThat(future.getUploadSessionUrl()).isNotNull();
189+
assertThat(future.getUploadSessionUrl()).contains("/resumable/upload");
190+
assertThat(response.getName()).isEqualTo("it-grpc-delegation.txt");
191+
assertThat(response.getSize()).isEqualTo(Files.size(file));
192+
}
193+
}
194+
}
195+
196+
@Test
197+
void testChannelGuards_grpcChannelOnly_throwsFailedPreconditionException(@TempDir Path tempDir)
198+
throws Exception {
199+
Path file =
200+
createTempFile(
201+
tempDir, "guard-test.txt", "guard test data".getBytes(StandardCharsets.UTF_8));
202+
203+
try (ResumableUploadServiceClient grpcClient =
204+
TestClientInitializer.createGrpcResumableUploadClient(
205+
NoCredentialsProvider.create(), SHOWCASE_CHUNK_SIZE)) {
206+
UploadMediaRequest request =
207+
UploadMediaRequest.newBuilder().setName("guard-test.txt").build();
208+
209+
try (InputStream stream1 = Files.newInputStream(file)) {
210+
FailedPreconditionException syncException =
211+
assertThrows(
212+
FailedPreconditionException.class, () -> grpcClient.uploadMedia(request, stream1));
213+
assertThat(syncException.getMessage())
214+
.contains("Resumable uploads execute over HTTP/REST and require credentials");
215+
}
216+
217+
FailedPreconditionException callableException =
218+
assertThrows(FailedPreconditionException.class, () -> grpcClient.uploadMediaCallable());
219+
assertThat(callableException.getMessage())
220+
.contains("Resumable uploads execute over HTTP/REST and require credentials");
221+
}
222+
}
223+
224+
private static Path createTempFile(Path dir, String fileName, byte[] data) throws IOException {
225+
Path path = dir.resolve(fileName);
226+
Files.write(path, data);
227+
return path;
228+
}
229+
230+
private static Path createTempFile(Path dir, String fileName, int size) throws IOException {
231+
byte[] data = new byte[size];
232+
for (int i = 0; i < size; i++) {
233+
data[i] = (byte) (i % 256);
234+
}
235+
return createTempFile(dir, fileName, data);
236+
}
237+
}

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestClientInitializer.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,29 @@
1717
package com.google.showcase.v1beta1.it.util;
1818

1919
import com.google.api.client.http.javanet.NetHttpTransport;
20+
import com.google.api.gax.core.CredentialsProvider;
2021
import com.google.api.gax.core.NoCredentialsProvider;
22+
import com.google.api.gax.grpc.GrpcTransportChannel;
2123
import com.google.api.gax.httpjson.HttpJsonClientInterceptor;
2224
import com.google.api.gax.longrunning.OperationSnapshot;
2325
import com.google.api.gax.longrunning.OperationTimedPollAlgorithm;
2426
import com.google.api.gax.retrying.RetrySettings;
27+
import com.google.api.gax.rpc.FixedTransportChannelProvider;
2528
import com.google.api.gax.rpc.StatusCode;
29+
import com.google.api.gax.rpc.TransportChannel;
2630
import com.google.api.gax.rpc.TransportChannelProvider;
2731
import com.google.api.gax.rpc.UnaryCallSettings;
2832
import com.google.api.gax.tracing.ApiTracerFactory;
33+
import com.google.auth.Credentials;
2934
import com.google.common.collect.ImmutableList;
3035
import com.google.showcase.v1beta1.ComplianceClient;
3136
import com.google.showcase.v1beta1.ComplianceSettings;
3237
import com.google.showcase.v1beta1.EchoClient;
3338
import com.google.showcase.v1beta1.EchoSettings;
3439
import com.google.showcase.v1beta1.IdentityClient;
3540
import com.google.showcase.v1beta1.IdentitySettings;
41+
import com.google.showcase.v1beta1.ResumableUploadServiceClient;
42+
import com.google.showcase.v1beta1.ResumableUploadServiceSettings;
3643
import com.google.showcase.v1beta1.SequenceServiceClient;
3744
import com.google.showcase.v1beta1.SequenceServiceSettings;
3845
import com.google.showcase.v1beta1.WaitRequest;
@@ -42,7 +49,10 @@
4249
import io.grpc.ManagedChannelBuilder;
4350
import java.io.IOException;
4451
import java.util.List;
52+
import java.util.Map;
4553
import java.util.Set;
54+
import java.util.concurrent.Executor;
55+
import java.util.concurrent.ScheduledExecutorService;
4656

4757
public class TestClientInitializer {
4858

@@ -539,4 +549,118 @@ public static SequenceServiceClient createHttpJsonSequenceClientWithRetrySetting
539549
.build());
540550
return SequenceServiceClient.create(settingsBuilder.build());
541551
}
552+
553+
public static ResumableUploadServiceClient createHttpJsonResumableUploadClient(int chunkSize)
554+
throws Exception {
555+
ResumableUploadServiceSettings.Builder settingsBuilder =
556+
ResumableUploadServiceSettings.newHttpJsonBuilder();
557+
settingsBuilder
558+
.setCredentialsProvider(NoCredentialsProvider.create())
559+
.setTransportChannelProvider(
560+
ResumableUploadServiceSettings.defaultHttpJsonTransportProviderBuilder()
561+
.setHttpTransport(new NetHttpTransport.Builder().doNotValidateCertificate().build())
562+
.setEndpoint(DEFAULT_HTTPJSON_ENDPOINT)
563+
.build());
564+
settingsBuilder.uploadMediaSettings().setChunkSize(chunkSize);
565+
return ResumableUploadServiceClient.create(settingsBuilder.build());
566+
}
567+
568+
public static ResumableUploadServiceClient createGrpcResumableUploadClient(
569+
CredentialsProvider credentialsProvider, int chunkSize) throws Exception {
570+
// Use plaintext gRPC transport with HTTP/JSON endpoint for the internal REST upload stub.
571+
GrpcTransportChannel grpcChannel =
572+
GrpcTransportChannel.create(
573+
ManagedChannelBuilder.forTarget(DEFAULT_GRPC_ENDPOINT).usePlaintext().build());
574+
ResumableUploadServiceSettings.Builder settingsBuilder =
575+
ResumableUploadServiceSettings.newBuilder()
576+
.setCredentialsProvider(credentialsProvider)
577+
.setTransportChannelProvider(new AutoClosingTransportChannelProvider(grpcChannel))
578+
.setEndpoint(DEFAULT_HTTPJSON_ENDPOINT);
579+
settingsBuilder.uploadMediaSettings().setChunkSize(chunkSize);
580+
return ResumableUploadServiceClient.create(settingsBuilder.build());
581+
}
582+
583+
private static final class AutoClosingTransportChannelProvider
584+
implements TransportChannelProvider {
585+
private final TransportChannelProvider delegate;
586+
587+
AutoClosingTransportChannelProvider(TransportChannel transportChannel) {
588+
this.delegate = FixedTransportChannelProvider.create(transportChannel);
589+
}
590+
591+
@Override
592+
public boolean shouldAutoClose() {
593+
return true;
594+
}
595+
596+
@Override
597+
public boolean needsExecutor() {
598+
return delegate.needsExecutor();
599+
}
600+
601+
@Override
602+
public TransportChannelProvider withExecutor(ScheduledExecutorService executor) {
603+
return delegate.withExecutor(executor);
604+
}
605+
606+
@Override
607+
public TransportChannelProvider withExecutor(Executor executor) {
608+
return delegate.withExecutor(executor);
609+
}
610+
611+
@Override
612+
public boolean needsHeaders() {
613+
return delegate.needsHeaders();
614+
}
615+
616+
@Override
617+
public TransportChannelProvider withHeaders(Map<String, String> headers) {
618+
return delegate.withHeaders(headers);
619+
}
620+
621+
@Override
622+
public boolean needsEndpoint() {
623+
return delegate.needsEndpoint();
624+
}
625+
626+
@Override
627+
public TransportChannelProvider withEndpoint(String endpoint) {
628+
return delegate.withEndpoint(endpoint);
629+
}
630+
631+
@Override
632+
public boolean acceptsPoolSize() {
633+
return delegate.acceptsPoolSize();
634+
}
635+
636+
@Override
637+
public TransportChannelProvider withPoolSize(int size) {
638+
return delegate.withPoolSize(size);
639+
}
640+
641+
@Override
642+
public TransportChannel getTransportChannel() throws IOException {
643+
return delegate.getTransportChannel();
644+
}
645+
646+
@Override
647+
public String getTransportName() {
648+
return delegate.getTransportName();
649+
}
650+
651+
@Override
652+
public boolean needsCredentials() {
653+
return delegate.needsCredentials();
654+
}
655+
656+
@Override
657+
public TransportChannelProvider withCredentials(Credentials credentials) {
658+
return delegate.withCredentials(credentials);
659+
}
660+
661+
@Override
662+
public String getEndpoint() {
663+
return delegate.getEndpoint();
664+
}
665+
}
542666
}

0 commit comments

Comments
 (0)