diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2.java new file mode 100644 index 0000000000..f8284a09c8 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2.java @@ -0,0 +1,49 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.admin.v2; + +import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; +import java.io.IOException; + +/** + * Modern Cloud Bigtable Instance Admin Client. + * + *

This client extends the auto-generated {@link BaseBigtableInstanceAdminClient} to provide + * manual overrides and additional convenience methods for Critical User Journeys (CUJs) that the + * GAPIC generator cannot handle natively. + */ +public class BigtableInstanceAdminClientV2 extends BaseBigtableInstanceAdminClient { + + protected BigtableInstanceAdminClientV2(BaseBigtableInstanceAdminSettings settings) + throws IOException { + super(settings); + } + + protected BigtableInstanceAdminClientV2(BigtableInstanceAdminStub stub) { + super(stub); + } + + /** Constructs an instance of BigtableInstanceAdminClientV2 with the given settings. */ + public static final BigtableInstanceAdminClientV2 createClient( + BaseBigtableInstanceAdminSettings settings) throws IOException { + return new BigtableInstanceAdminClientV2(settings); + } + + /** Constructs an instance of BigtableInstanceAdminClientV2 with the given stub. */ + public static final BigtableInstanceAdminClientV2 createClient(BigtableInstanceAdminStub stub) { + return new BigtableInstanceAdminClientV2(stub); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2.java new file mode 100644 index 0000000000..f61f2f0904 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2.java @@ -0,0 +1,338 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.admin.v2; + +import com.google.api.core.ApiFunction; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.gax.grpc.GrpcCallSettings; +import com.google.api.gax.grpc.GrpcCallableFactory; +import com.google.api.gax.grpc.ProtoOperationTransformers.MetadataTransformer; +import com.google.api.gax.grpc.ProtoOperationTransformers.ResponseTransformer; +import com.google.api.gax.longrunning.OperationSnapshot; +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.ApiExceptions; +import com.google.api.gax.rpc.ClientContext; +import com.google.api.gax.rpc.OperationCallSettings; +import com.google.api.gax.rpc.OperationCallable; +import com.google.api.gax.rpc.UnaryCallSettings; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.bigtable.admin.v2.OptimizeRestoredTableMetadata; +import com.google.cloud.bigtable.admin.v2.models.ConsistencyRequest; +import com.google.cloud.bigtable.admin.v2.models.OptimizeRestoredTableOperationToken; +import com.google.cloud.bigtable.admin.v2.models.RestoredTableResult; +import com.google.cloud.bigtable.admin.v2.stub.AwaitConsistencyCallable; +import com.google.cloud.bigtable.admin.v2.stub.BigtableTableAdminStub; +import com.google.cloud.bigtable.admin.v2.stub.BigtableTableAdminStubSettings; +import com.google.common.base.Strings; +import com.google.longrunning.Operation; +import com.google.protobuf.Empty; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.Marshaller; +import io.grpc.MethodDescriptor.MethodType; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ExecutionException; +import org.threeten.bp.Duration; + +/** + * Modern Cloud Bigtable Table Admin Client. + * + *

This client extends the auto-generated {@link BaseBigtableTableAdminClient} to provide manual + * overrides and additional convenience methods for Critical User Journeys (CUJs) that the GAPIC + * generator cannot handle natively (e.g., chained Long Running Operations, Consistency Polling). + */ +public class BigtableTableAdminClientV2 extends BaseBigtableTableAdminClient { + private final AwaitConsistencyCallable awaitConsistencyCallable; + private final OperationCallable + optimizeRestoredTableOperationBaseCallable; + + protected BigtableTableAdminClientV2(BaseBigtableTableAdminSettings settings) throws IOException { + super(settings); + this.awaitConsistencyCallable = + createAwaitConsistencyCallable((BigtableTableAdminStubSettings) settings.getStubSettings()); + this.optimizeRestoredTableOperationBaseCallable = + createOptimizeRestoredTableOperationBaseCallable( + (BigtableTableAdminStubSettings) settings.getStubSettings()); + } + + protected BigtableTableAdminClientV2(BigtableTableAdminStub stub) { + super(stub); + this.awaitConsistencyCallable = null; + this.optimizeRestoredTableOperationBaseCallable = null; + } + + @com.google.common.annotations.VisibleForTesting + BigtableTableAdminClientV2( + BigtableTableAdminStub stub, + AwaitConsistencyCallable awaitConsistencyCallable, + OperationCallable + optimizeRestoredTableOperationBaseCallable) { + super(stub); + this.awaitConsistencyCallable = awaitConsistencyCallable; + this.optimizeRestoredTableOperationBaseCallable = optimizeRestoredTableOperationBaseCallable; + } + + private AwaitConsistencyCallable createAwaitConsistencyCallable( + BigtableTableAdminStubSettings settings) throws IOException { + ClientContext clientContext = ClientContext.create(settings); + // TODO(igorbernstein2): expose polling settings + RetrySettings pollingSettings = + RetrySettings.newBuilder() + .setTotalTimeout( + settings.checkConsistencySettings().getRetrySettings().getTotalTimeout()) + .setInitialRetryDelay(Duration.ofSeconds(10)) + .setRetryDelayMultiplier(1.0) + .setMaxRetryDelay(Duration.ofSeconds(10)) + .setInitialRpcTimeout(Duration.ZERO) + .setMaxRpcTimeout(Duration.ZERO) + .setRpcTimeoutMultiplier(1.0) + .build(); + + return AwaitConsistencyCallable.create( + getStub().generateConsistencyTokenCallable(), + getStub().checkConsistencyCallable(), + clientContext, + pollingSettings); + } + + private OperationCallable + createOptimizeRestoredTableOperationBaseCallable(BigtableTableAdminStubSettings settings) + throws IOException { + ClientContext clientContext = ClientContext.create(settings); + + GrpcCallSettings unusedInitialCallSettings = + GrpcCallSettings.create( + MethodDescriptor.newBuilder() + .setType(MethodType.UNARY) + .setFullMethodName( + "google.bigtable.admin.v2.BigtableTableAdmin/OptimizeRestoredTable") + .setRequestMarshaller( + new Marshaller() { + @Override + public InputStream stream(Void value) { + throw new UnsupportedOperationException("not used"); + } + + @Override + public Void parse(InputStream stream) { + throw new UnsupportedOperationException("not used"); + } + }) + .setResponseMarshaller( + new Marshaller() { + @Override + public InputStream stream(Operation value) { + throw new UnsupportedOperationException("not used"); + } + + @Override + public Operation parse(InputStream stream) { + throw new UnsupportedOperationException("not used"); + } + }) + .build()); + + final MetadataTransformer protoMetadataTransformer = + MetadataTransformer.create(OptimizeRestoredTableMetadata.class); + + final ResponseTransformer protoResponseTransformer = + ResponseTransformer.create(com.google.protobuf.Empty.class); + + OperationCallSettings operationCallSettings = + OperationCallSettings.newBuilder() + .setInitialCallSettings( + UnaryCallSettings.newUnaryCallSettingsBuilder() + .setSimpleTimeoutNoRetries(Duration.ZERO) + .build()) + .setMetadataTransformer( + new ApiFunction() { + @Override + public OptimizeRestoredTableMetadata apply(OperationSnapshot input) { + return protoMetadataTransformer.apply(input); + } + }) + .setResponseTransformer( + new ApiFunction() { + @Override + public Empty apply(OperationSnapshot input) { + return protoResponseTransformer.apply(input); + } + }) + .setPollingAlgorithm( + OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(500L)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofMillis(5000L)) + .setInitialRpcTimeout(Duration.ZERO) + .setRpcTimeoutMultiplier(1.0) + .setMaxRpcTimeout(Duration.ZERO) + .setTotalTimeout(Duration.ofMillis(600000L)) + .build())) + .build(); + + return GrpcCallableFactory.createOperationCallable( + unusedInitialCallSettings, + operationCallSettings, + clientContext, + getStub().getOperationsStub()); + } + + /** Constructs an instance of BigtableTableAdminClientV2 with the given settings. */ + public static final BigtableTableAdminClientV2 createClient( + BaseBigtableTableAdminSettings settings) throws IOException { + return new BigtableTableAdminClientV2(settings); + } + + /** Constructs an instance of BigtableTableAdminClientV2 with the given stub. */ + public static final BigtableTableAdminClientV2 createClient(BigtableTableAdminStub stub) { + return new BigtableTableAdminClientV2(stub); + } + + /** + * Awaits the completion of the "Optimize Restored Table" operation. + * + *

This method blocks until the restore operation is complete, extracts the optimization token, + * and returns an ApiFuture for the optimization phase. + * + * @param restoreFuture The future returned by restoreTableAsync(). + * @return An ApiFuture that tracks the optimization progress. + */ + public ApiFuture awaitOptimizeRestoredTable(ApiFuture restoreFuture) { + // 1. Block and wait for the restore operation to complete + RestoredTableResult result; + try { + result = restoreFuture.get(); + } catch (Exception e) { + throw new RuntimeException("Restore operation failed", e); + } + + // 2. Extract the operation token from the result + // (RestoredTableResult already wraps the OptimizeRestoredTableOperationToken) + OptimizeRestoredTableOperationToken token = result.getOptimizeRestoredTableOperationToken(); + + if (token == null || Strings.isNullOrEmpty(token.getOperationName())) { + // If there is no optimization operation, return immediate success. + return ApiFutures.immediateFuture(Empty.getDefaultInstance()); + } + + // 3. Return the future for the optimization operation + return getOptimizeRestoredTableCallable().resumeFutureCall(token.getOperationName()); + } + + /** + * Awaits a restored table is fully optimized. + * + *

Sample code + * + *

{@code
+   * RestoredTableResult result =
+   *     client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId));
+   * client.awaitOptimizeRestoredTable(result.getOptimizeRestoredTableOperationToken());
+   * }
+ */ + public void awaitOptimizeRestoredTable(OptimizeRestoredTableOperationToken token) + throws ExecutionException, InterruptedException { + awaitOptimizeRestoredTableAsync(token).get(); + } + + /** + * Awaits a restored table is fully optimized asynchronously. + * + *

Sample code + * + *

{@code
+   * RestoredTableResult result =
+   *     client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId));
+   * ApiFuture future = client.awaitOptimizeRestoredTableAsync(
+   *     result.getOptimizeRestoredTableOperationToken());
+   *
+   * ApiFutures.addCallback(
+   *   future,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Void unused) {
+   *       System.out.println("The optimization of the restored table is done.");
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor()
+   * );
+   * }
+ */ + public ApiFuture awaitOptimizeRestoredTableAsync( + OptimizeRestoredTableOperationToken token) { + ApiFuture emptyFuture = + getOptimizeRestoredTableCallable().resumeFutureCall(token.getOperationName()); + return ApiFutures.transform( + emptyFuture, + new com.google.api.core.ApiFunction() { + @Override + public Void apply(Empty input) { + return null; + } + }, + com.google.common.util.concurrent.MoreExecutors.directExecutor()); + } + + /** + * Polls an existing consistency token until table replication is consistent across all clusters. + * Useful for checking consistency of a token generated in a separate process. Blocks until + * completion. + * + * @param tableName The fully qualified table name to check. + * @param consistencyToken The token to poll. + */ + public void waitForConsistency(String tableName, String consistencyToken) { + ApiExceptions.callAndTranslateApiException( + waitForConsistencyAsync(tableName, consistencyToken)); + } + + /** + * Asynchronously polls the consistency token. Returns a future that completes when table + * replication is consistent across all clusters. + * + * @param tableName The fully qualified table name to check. + * @param consistencyToken The token to poll. + */ + public ApiFuture waitForConsistencyAsync(String tableName, String consistencyToken) { + return getAwaitConsistencyCallable() + .futureCall(ConsistencyRequest.forReplicationFromTableName(tableName, consistencyToken)); + } + + private UnaryCallable getAwaitConsistencyCallable() { + if (awaitConsistencyCallable != null) { + return awaitConsistencyCallable; + } + throw new IllegalStateException( + "AwaitConsistencyCallable not initialized. BigtableTableAdminClientV2 must be " + + "initialized via settings to use this functionality."); + } + + private OperationCallable + getOptimizeRestoredTableCallable() { + if (optimizeRestoredTableOperationBaseCallable != null) { + return optimizeRestoredTableOperationBaseCallable; + } + throw new IllegalStateException( + "OptimizeRestoredTableCallable not initialized. BigtableTableAdminClientV2 must be " + + "initialized via settings to use this functionality."); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallable.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallable.java index a8ccdd9704..f772421b8d 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallable.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/stub/AwaitConsistencyCallable.java @@ -51,7 +51,7 @@ *

This callable wraps GenerateConsistencyToken and CheckConsistency RPCs. It will generate a * token then poll until isConsistent is true. */ -class AwaitConsistencyCallable extends UnaryCallable { +public class AwaitConsistencyCallable extends UnaryCallable { private final UnaryCallable generateCallable; private final UnaryCallable checkCallable; @@ -59,7 +59,7 @@ class AwaitConsistencyCallable extends UnaryCallable { @Nullable private final TableAdminRequestContext requestContext; - static AwaitConsistencyCallable create( + public static AwaitConsistencyCallable create( UnaryCallable generateCallable, UnaryCallable checkCallable, @@ -79,7 +79,7 @@ static AwaitConsistencyCallable create( generateCallable, checkCallable, retryingExecutor, requestContext); } - static AwaitConsistencyCallable create( + public static AwaitConsistencyCallable create( UnaryCallable generateCallable, UnaryCallable checkCallable, diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2Test.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2Test.java new file mode 100644 index 0000000000..7abf4941a6 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientV2Test.java @@ -0,0 +1,48 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.admin.v2; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; + +@RunWith(JUnit4.class) +public class BigtableInstanceAdminClientV2Test { + + @Test + public void testCreateWithStub() { + BigtableInstanceAdminStub mockStub = Mockito.mock(BigtableInstanceAdminStub.class); + BigtableInstanceAdminClientV2 client = BigtableInstanceAdminClientV2.createClient(mockStub); + + assertThat(client).isNotNull(); + } + + @Test + public void testCreateClientWithSettings() throws Exception { + BaseBigtableInstanceAdminSettings settings = + BaseBigtableInstanceAdminSettings.newBuilder() + .setCredentialsProvider(com.google.api.gax.core.NoCredentialsProvider.create()) + .build(); + try (BigtableInstanceAdminClientV2 settingsClient = + BigtableInstanceAdminClientV2.createClient(settings)) { + assertThat(settingsClient).isNotNull(); + } + } +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2Test.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2Test.java new file mode 100644 index 0000000000..104088d2b3 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableTableAdminClientV2Test.java @@ -0,0 +1,156 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.admin.v2; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.rpc.OperationCallable; +import com.google.bigtable.admin.v2.OptimizeRestoredTableMetadata; +import com.google.cloud.bigtable.admin.v2.models.ConsistencyRequest; +import com.google.cloud.bigtable.admin.v2.models.OptimizeRestoredTableOperationToken; +import com.google.cloud.bigtable.admin.v2.models.RestoredTableResult; +import com.google.cloud.bigtable.admin.v2.stub.AwaitConsistencyCallable; +import com.google.cloud.bigtable.admin.v2.stub.BigtableTableAdminStub; +import com.google.cloud.bigtable.admin.v2.stub.EnhancedBigtableTableAdminStub; +import com.google.protobuf.Empty; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +@RunWith(JUnit4.class) +public class BigtableTableAdminClientV2Test { + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + private static final String TABLE_NAME = + "projects/my-project/instances/my-instance/tables/my-table"; + + @Mock private BigtableTableAdminStub mockStub; + + @Mock private AwaitConsistencyCallable mockAwaitConsistencyCallable; + + @Mock + private OperationCallable + mockOptimizeRestoredTableCallable; + + private BigtableTableAdminClientV2 client; + + @Before + public void setUp() { + client = + new BigtableTableAdminClientV2( + mockStub, mockAwaitConsistencyCallable, mockOptimizeRestoredTableCallable); + } + + @Test + public void testWaitForConsistencyWithToken() { + // Setup + + String token = "my-token"; + ConsistencyRequest expectedRequest = + ConsistencyRequest.forReplicationFromTableName(TABLE_NAME, token); + + final AtomicBoolean wasCalled = new AtomicBoolean(false); + + Mockito.when(mockAwaitConsistencyCallable.futureCall(expectedRequest)) + .thenAnswer( + (Answer>) + invocationOnMock -> { + wasCalled.set(true); + return ApiFutures.immediateFuture(null); + }); + + // Execute + client.waitForConsistency(TABLE_NAME, token); + + // Verify + assertThat(wasCalled.get()).isTrue(); + } + + @Test + public void testAwaitOptimizeRestoredTable() throws Exception { + // Setup + + String optimizeToken = "my-optimization-token"; + + // 1. Mock the Token + OptimizeRestoredTableOperationToken mockToken = + Mockito.mock(OptimizeRestoredTableOperationToken.class); + Mockito.when(mockToken.getOperationName()).thenReturn(optimizeToken); + + // 2. Mock the Result (wrapping the token) + RestoredTableResult mockResult = Mockito.mock(RestoredTableResult.class); + Mockito.when(mockResult.getOptimizeRestoredTableOperationToken()).thenReturn(mockToken); + + // 3. Mock the Input Future (returning the result) + ApiFuture mockRestoreFuture = Mockito.mock(ApiFuture.class); + Mockito.when(mockRestoreFuture.get()).thenReturn(mockResult); + + // 4. Mock the Stub's behavior (resuming the Optimize Op) + OperationFuture mockOptimizeOp = + Mockito.mock(OperationFuture.class); + Mockito.when(mockOptimizeRestoredTableCallable.resumeFutureCall(optimizeToken)) + .thenReturn(mockOptimizeOp); + + // Execute + ApiFuture result = client.awaitOptimizeRestoredTable(mockRestoreFuture); + + // Verify + assertThat(result).isEqualTo(mockOptimizeOp); + Mockito.verify(mockOptimizeRestoredTableCallable).resumeFutureCall(optimizeToken); + } + + @Test + public void testAwaitOptimizeRestoredTable_NoOp() throws Exception { + // Setup: Result with NO optimization token (null or empty) + RestoredTableResult mockResult = Mockito.mock(RestoredTableResult.class); + Mockito.when(mockResult.getOptimizeRestoredTableOperationToken()).thenReturn(null); + + // Mock the Input Future + ApiFuture mockRestoreFuture = Mockito.mock(ApiFuture.class); + Mockito.when(mockRestoreFuture.get()).thenReturn(mockResult); + + // Execute + ApiFuture result = client.awaitOptimizeRestoredTable(mockRestoreFuture); + + // Verify: Returns immediate success (Empty) without calling the stub + assertThat(result.get()).isEqualTo(Empty.getDefaultInstance()); + } + + @Test + public void testCreateClientWithSettings() throws Exception { + BaseBigtableTableAdminSettings settings = + BaseBigtableTableAdminSettings.newBuilder() + .setCredentialsProvider(com.google.api.gax.core.NoCredentialsProvider.create()) + .build(); + try (BigtableTableAdminClientV2 settingsClient = + BigtableTableAdminClientV2.createClient(settings)) { + // Verify that the underlying stub is NOT an Enhanced stub by default + // but the client has successfully initialized its own callables. + assertThat(settingsClient.getStub()).isNotInstanceOf(EnhancedBigtableTableAdminStub.class); + } + } +}