Skip to content

Commit db10f22

Browse files
committed
test(showcase): add integration tests for resumable upload
1 parent 4ca74ac commit db10f22

2 files changed

Lines changed: 299 additions & 0 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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.grpc.GrpcTransportChannel;
25+
import com.google.api.gax.rpc.FailedPreconditionException;
26+
import com.google.api.gax.rpc.ResumableUploadCallSettings;
27+
import com.google.api.gax.rpc.ResumableUploadFuture;
28+
import com.google.auth.Credentials;
29+
import com.google.auth.oauth2.AccessToken;
30+
import com.google.auth.oauth2.OAuth2Credentials;
31+
import com.google.showcase.v1beta1.ResumableUploadServiceClient;
32+
import com.google.showcase.v1beta1.UploadMediaRequest;
33+
import com.google.showcase.v1beta1.UploadMediaResponse;
34+
import com.google.showcase.v1beta1.it.util.TestClientInitializer;
35+
import java.io.ByteArrayInputStream;
36+
import java.io.IOException;
37+
import java.io.InputStream;
38+
import java.nio.charset.StandardCharsets;
39+
import java.nio.file.Files;
40+
import java.nio.file.Path;
41+
import java.util.Date;
42+
import java.util.concurrent.TimeUnit;
43+
import org.junit.jupiter.api.AfterAll;
44+
import org.junit.jupiter.api.BeforeAll;
45+
import org.junit.jupiter.api.Test;
46+
import org.junit.jupiter.api.io.TempDir;
47+
48+
/**
49+
* Integration tests for generated {@link ResumableUploadServiceClient} against the Showcase server.
50+
*/
51+
class ITResumableUpload {
52+
53+
private static final int SHOWCASE_CHUNK_SIZE = 256 * 1024; // 256KB
54+
private static final Credentials DUMMY_CREDENTIALS =
55+
OAuth2Credentials.create(new AccessToken("fake-token", new Date(Long.MAX_VALUE)));
56+
private static ResumableUploadServiceClient client;
57+
58+
@BeforeAll
59+
static void createClients() throws Exception {
60+
client = TestClientInitializer.createHttpJsonResumableUploadClient(SHOWCASE_CHUNK_SIZE);
61+
}
62+
63+
@AfterAll
64+
static void destroyClients() throws InterruptedException {
65+
if (client != null) {
66+
client.close();
67+
client.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
68+
}
69+
}
70+
71+
@Test
72+
void testGeneratedClient_uploadMedia_synchronousConvenienceMethod(@TempDir Path tempDir)
73+
throws Exception {
74+
Path file =
75+
createTempFile(
76+
tempDir,
77+
"it-client-sync.txt",
78+
"Hello from generated ResumableUploadServiceClient synchronous convenience method!"
79+
.getBytes(StandardCharsets.UTF_8));
80+
UploadMediaRequest request =
81+
UploadMediaRequest.newBuilder().setName("it-client-sync.txt").build();
82+
83+
try (InputStream stream = Files.newInputStream(file)) {
84+
UploadMediaResponse response = client.uploadMedia(request, stream);
85+
assertThat(response.getName()).isEqualTo("it-client-sync.txt");
86+
assertThat(response.getSize()).isEqualTo(Files.size(file));
87+
}
88+
}
89+
90+
@Test
91+
void testGeneratedClient_uploadMediaCallable_asynchronousFutureCall(@TempDir Path tempDir)
92+
throws Exception {
93+
Path file =
94+
createTempFile(
95+
tempDir,
96+
"it-client-callable.txt",
97+
"Hello from generated ResumableUploadServiceClient callable futureCall!"
98+
.getBytes(StandardCharsets.UTF_8));
99+
UploadMediaRequest request =
100+
UploadMediaRequest.newBuilder().setName("it-client-callable.txt").build();
101+
102+
try (InputStream stream = Files.newInputStream(file)) {
103+
ResumableUploadFuture<UploadMediaResponse> future =
104+
client
105+
.uploadMediaCallable()
106+
.futureCall(request, stream, (ResumableUploadCallSettings) null);
107+
108+
UploadMediaResponse response = future.get(10, TimeUnit.SECONDS);
109+
assertThat(future.isDone()).isTrue();
110+
assertThat(future.isCancelled()).isFalse();
111+
assertThat(future.getUploadSessionUrl()).isNotNull();
112+
assertThat(future.getUploadSessionUrl()).contains("/resumable/upload");
113+
assertThat(response.getName()).isEqualTo("it-client-callable.txt");
114+
assertThat(response.getSize()).isEqualTo(Files.size(file));
115+
}
116+
}
117+
118+
@Test
119+
void testGeneratedClient_multiChunkUpload(@TempDir Path tempDir) throws Exception {
120+
// 600KB payload = 2 full 256KB chunks + 1 partial 88KB chunk
121+
int totalBytes = 600 * 1024;
122+
Path file = createTempFile(tempDir, "it-client-multi-chunk.txt", totalBytes);
123+
UploadMediaRequest request =
124+
UploadMediaRequest.newBuilder().setName("it-client-multi-chunk.txt").build();
125+
126+
try (InputStream stream = Files.newInputStream(file)) {
127+
UploadMediaResponse response = client.uploadMedia(request, stream);
128+
assertThat(response.getName()).isEqualTo("it-client-multi-chunk.txt");
129+
assertThat(response.getSize()).isEqualTo(Files.size(file));
130+
}
131+
}
132+
133+
@Test
134+
void testGeneratedClient_zeroByteUpload() throws Exception {
135+
UploadMediaRequest request =
136+
UploadMediaRequest.newBuilder().setName("it-client-zero-byte.txt").build();
137+
138+
try (InputStream stream = new ByteArrayInputStream(new byte[0])) {
139+
UploadMediaResponse response = client.uploadMedia(request, stream);
140+
assertThat(response.getName()).isEqualTo("it-client-zero-byte.txt");
141+
assertThat(response.getSize()).isEqualTo(0);
142+
}
143+
}
144+
145+
@Test
146+
void testGeneratedClient_exactChunkBoundaryUpload(@TempDir Path tempDir) throws Exception {
147+
// Exactly 2 full 256KB chunks (512KB total) -> triggers 0-byte finalize request
148+
int totalBytes = 512 * 1024;
149+
Path file = createTempFile(tempDir, "it-client-exact-chunks.txt", totalBytes);
150+
UploadMediaRequest request =
151+
UploadMediaRequest.newBuilder().setName("it-client-exact-chunks.txt").build();
152+
153+
try (InputStream stream = Files.newInputStream(file)) {
154+
UploadMediaResponse response = client.uploadMedia(request, stream);
155+
assertThat(response.getName()).isEqualTo("it-client-exact-chunks.txt");
156+
assertThat(response.getSize()).isEqualTo(Files.size(file));
157+
}
158+
}
159+
160+
@Test
161+
void testGeneratedClient_grpcClientDelegation_uploadMedia(@TempDir Path tempDir)
162+
throws Exception {
163+
Path file =
164+
createTempFile(
165+
tempDir,
166+
"it-grpc-delegation.txt",
167+
"Hello from generated ResumableUploadServiceClient gRPC delegation!"
168+
.getBytes(StandardCharsets.UTF_8));
169+
UploadMediaRequest request =
170+
UploadMediaRequest.newBuilder().setName("it-grpc-delegation.txt").build();
171+
172+
try (GrpcTransportChannel grpcChannel = TestClientInitializer.createPlaintextGrpcChannel();
173+
ResumableUploadServiceClient grpcClient =
174+
TestClientInitializer.createGrpcResumableUploadClient(
175+
grpcChannel,
176+
FixedCredentialsProvider.create(DUMMY_CREDENTIALS),
177+
SHOWCASE_CHUNK_SIZE)) {
178+
// 1. Synchronous convenience method delegation
179+
try (InputStream stream = Files.newInputStream(file)) {
180+
UploadMediaResponse response = grpcClient.uploadMedia(request, stream);
181+
assertThat(response.getName()).isEqualTo("it-grpc-delegation.txt");
182+
assertThat(response.getSize()).isEqualTo(Files.size(file));
183+
}
184+
185+
// 2. Asynchronous callable futureCall delegation
186+
try (InputStream stream = Files.newInputStream(file)) {
187+
ResumableUploadFuture<UploadMediaResponse> future =
188+
grpcClient
189+
.uploadMediaCallable()
190+
.futureCall(request, stream, (ResumableUploadCallSettings) null);
191+
UploadMediaResponse response = future.get(10, TimeUnit.SECONDS);
192+
assertThat(future.isDone()).isTrue();
193+
assertThat(future.isCancelled()).isFalse();
194+
assertThat(future.getUploadSessionUrl()).isNotNull();
195+
assertThat(future.getUploadSessionUrl()).contains("/resumable/upload");
196+
assertThat(response.getName()).isEqualTo("it-grpc-delegation.txt");
197+
assertThat(response.getSize()).isEqualTo(Files.size(file));
198+
}
199+
}
200+
}
201+
202+
@Test
203+
void testChannelGuards_grpcChannelOnly_throwsFailedPreconditionException(@TempDir Path tempDir)
204+
throws Exception {
205+
Path file =
206+
createTempFile(
207+
tempDir, "guard-test.txt", "guard test data".getBytes(StandardCharsets.UTF_8));
208+
209+
try (GrpcTransportChannel grpcChannel = TestClientInitializer.createPlaintextGrpcChannel();
210+
ResumableUploadServiceClient grpcClient =
211+
TestClientInitializer.createGrpcResumableUploadClient(
212+
grpcChannel, NoCredentialsProvider.create())) {
213+
UploadMediaRequest request =
214+
UploadMediaRequest.newBuilder().setName("guard-test.txt").build();
215+
216+
try (InputStream stream1 = Files.newInputStream(file)) {
217+
// 1. Verify synchronous convenience call fails fast
218+
FailedPreconditionException syncException =
219+
assertThrows(
220+
FailedPreconditionException.class, () -> grpcClient.uploadMedia(request, stream1));
221+
assertThat(syncException.getMessage())
222+
.contains("Resumable uploads execute over HTTP/REST and require credentials");
223+
}
224+
225+
// 2. Verify callable getter fails fast
226+
FailedPreconditionException callableException =
227+
assertThrows(FailedPreconditionException.class, () -> grpcClient.uploadMediaCallable());
228+
assertThat(callableException.getMessage())
229+
.contains("Resumable uploads execute over HTTP/REST and require credentials");
230+
}
231+
}
232+
233+
private static Path createTempFile(Path dir, String fileName, byte[] data) throws IOException {
234+
Path path = dir.resolve(fileName);
235+
Files.write(path, data);
236+
return path;
237+
}
238+
239+
private static Path createTempFile(Path dir, String fileName, int size) throws IOException {
240+
byte[] data = new byte[size];
241+
for (int i = 0; i < size; i++) {
242+
data[i] = (byte) (i % 256);
243+
}
244+
return createTempFile(dir, fileName, data);
245+
}
246+
}

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
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;
2629
import com.google.api.gax.rpc.TransportChannelProvider;
2730
import com.google.api.gax.rpc.UnaryCallSettings;
@@ -33,6 +36,8 @@
3336
import com.google.showcase.v1beta1.EchoSettings;
3437
import com.google.showcase.v1beta1.IdentityClient;
3538
import com.google.showcase.v1beta1.IdentitySettings;
39+
import com.google.showcase.v1beta1.ResumableUploadServiceClient;
40+
import com.google.showcase.v1beta1.ResumableUploadServiceSettings;
3641
import com.google.showcase.v1beta1.SequenceServiceClient;
3742
import com.google.showcase.v1beta1.SequenceServiceSettings;
3843
import com.google.showcase.v1beta1.WaitRequest;
@@ -539,4 +544,52 @@ public static SequenceServiceClient createHttpJsonSequenceClientWithRetrySetting
539544
.build());
540545
return SequenceServiceClient.create(settingsBuilder.build());
541546
}
547+
548+
public static ResumableUploadServiceClient createHttpJsonResumableUploadClient(int chunkSize)
549+
throws Exception {
550+
ResumableUploadServiceSettings.Builder settingsBuilder =
551+
ResumableUploadServiceSettings.newHttpJsonBuilder();
552+
settingsBuilder
553+
.setCredentialsProvider(NoCredentialsProvider.create())
554+
.setTransportChannelProvider(
555+
ResumableUploadServiceSettings.defaultHttpJsonTransportProviderBuilder()
556+
.setHttpTransport(new NetHttpTransport.Builder().doNotValidateCertificate().build())
557+
.setEndpoint(DEFAULT_HTTPJSON_ENDPOINT)
558+
.build());
559+
settingsBuilder.uploadMediaSettings().setChunkSize(chunkSize);
560+
return ResumableUploadServiceClient.create(settingsBuilder.build());
561+
}
562+
563+
public static GrpcTransportChannel createPlaintextGrpcChannel() {
564+
return GrpcTransportChannel.create(
565+
ManagedChannelBuilder.forTarget(DEFAULT_GRPC_ENDPOINT).usePlaintext().build());
566+
}
567+
568+
public static ResumableUploadServiceClient createGrpcResumableUploadClient(
569+
GrpcTransportChannel grpcChannel, CredentialsProvider credentialsProvider) throws Exception {
570+
// Showcase serves both plaintext gRPC and HTTP/1.1 REST on port 7469.
571+
// Providing a plaintext GrpcTransportChannel keeps gRPC traffic on DEFAULT_GRPC_ENDPOINT
572+
// ("localhost:7469") while configuring the client endpoint to DEFAULT_HTTPJSON_ENDPOINT
573+
// ("http://localhost:7469") so the internal HTTP/REST upload stub uses http:// instead of
574+
// defaulting to https://.
575+
ResumableUploadServiceSettings settings =
576+
ResumableUploadServiceSettings.newBuilder()
577+
.setCredentialsProvider(credentialsProvider)
578+
.setTransportChannelProvider(FixedTransportChannelProvider.create(grpcChannel))
579+
.setEndpoint(DEFAULT_HTTPJSON_ENDPOINT)
580+
.build();
581+
return ResumableUploadServiceClient.create(settings);
582+
}
583+
584+
public static ResumableUploadServiceClient createGrpcResumableUploadClient(
585+
GrpcTransportChannel grpcChannel, CredentialsProvider credentialsProvider, int chunkSize)
586+
throws Exception {
587+
ResumableUploadServiceSettings.Builder settingsBuilder =
588+
ResumableUploadServiceSettings.newBuilder()
589+
.setCredentialsProvider(credentialsProvider)
590+
.setTransportChannelProvider(FixedTransportChannelProvider.create(grpcChannel))
591+
.setEndpoint(DEFAULT_HTTPJSON_ENDPOINT);
592+
settingsBuilder.uploadMediaSettings().setChunkSize(chunkSize);
593+
return ResumableUploadServiceClient.create(settingsBuilder.build());
594+
}
542595
}

0 commit comments

Comments
 (0)