From 3556d723cfa9ce33f8f8b1ea4cd1a162166dc475 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 09:59:06 -0500 Subject: [PATCH 01/28] feat: ability to update credentials on long running client --- .../connection/MutableCredentials.java | 86 ++++++++++++++++ .../connection/ITMutableCredentialsTest.java | 94 ++++++++++++++++++ .../connection/MutableCredentialsTest.java | 98 +++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java new file mode 100644 index 0000000000..82326bdac6 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -0,0 +1,86 @@ +/* + * 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 + * + * 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 com.google.cloud.spanner.connection; + +import com.google.auth.Credentials; +import com.google.auth.oauth2.ServiceAccountCredentials; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped + * {@link ServiceAccountCredentials} instance. + * + *

This class is intended for scenarios where an application needs to rotate or replace the + * underlying service account credentials for a running Spanner Client. + * + *

All operations inherited from {@link Credentials} are forwarded to the current delegate, + * including request metadata retrieval and token refresh. Calling + * {@link #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped + * credentials instance created from the same scopes that were provided when this object was + * constructed. + */ +public class MutableCredentials extends Credentials { + ServiceAccountCredentials delegate; + List scopes; + + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this. scopes = scopes; + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } + + /** + * Replaces the current delegate with a newly scoped credentials instance. + * + *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were + * supplied when this {@link MutableCredentials} instance was created. + * + * @param credentials the new base service account credentials to scope and use for client + * authorization. + */ + public void updateCredentials(ServiceAccountCredentials credentials) { + delegate =(ServiceAccountCredentials) credentials.createScoped(scopes); + } + + @Override + public String getAuthenticationType() { + return delegate.getAuthenticationType(); + } + + @Override + public Map> getRequestMetadata(URI uri) throws IOException { + return delegate.getRequestMetadata(uri); + } + + @Override + public boolean hasRequestMetadata() { + return delegate.hasRequestMetadata(); + } + + @Override + public boolean hasRequestMetadataOnly() { + return delegate.hasRequestMetadataOnly(); + } + + @Override + public void refresh() throws IOException { + delegate.refresh(); + } +} + diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java new file mode 100644 index 0000000000..8cd37a3962 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -0,0 +1,94 @@ +/* + * 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 + * + * 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 com.google.cloud.spanner.connection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SerialIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(SerialIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITMutableCredentialsTest extends ITAbstractSpannerTest { + private static final String INVALID_KEY_FILE = + ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + + @Test + public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { + assumeTrue("This test requires a service account key file", hasValidKeyFile()); + + GoogleCredentials credentialsFromFile; + try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + credentialsFromFile = GoogleCredentials.fromStream(stream); + } + assumeTrue( + "This test requires service account credentials", + credentialsFromFile instanceof ServiceAccountCredentials); + + ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; + ServiceAccountCredentials invalidCredentials; + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_FILE))) { + invalidCredentials = ServiceAccountCredentials.fromStream(stream); + } + + List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); + MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); + + StringBuilder uri = + extractConnectionUrl(getTestEnv().getTestHelper().getOptions(), getDatabase()); + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri(uri.toString()) + .setCredentials(mutableCredentials) + .build(); + + try (Connection connection = options.getConnection()) { + try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 1"))) { + assertTrue(rs.next()); + } + + mutableCredentials.updateCredentials(invalidCredentials); + + try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 2"))) { + rs.next(); + fail("Expected UNAUTHENTICATED after switching to invalid credentials"); + } catch (SpannerException e) { + assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); + } + } finally { + closeSpanner(); + } + } +} \ No newline at end of file diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java new file mode 100644 index 0000000000..8447802bb4 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -0,0 +1,98 @@ +/* + * 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 + * + * 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 com.google.cloud.spanner.connection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.auth.oauth2.ServiceAccountCredentials; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MutableCredentialsTest { + ServiceAccountCredentials initialCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials initialScopedCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials updatedCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials updatedScopedCredentials = mock(ServiceAccountCredentials.class); + List scopes = Arrays.asList("scope-a", "scope-b"); + Map> initialMetadata = + Collections.singletonMap("Authorization", Collections.singletonList("v1")); + Map> updatedMetadata = + Collections.singletonMap("Authorization", Collections.singletonList("v2")); + String initialAuthType = "auth-1"; + String updatedAuthType = "auth-2"; + + @Test + public void testCreateMutableCredentialsAndUpdate() throws IOException { + setupInitialCredentials(); + setupUpdatedCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + + assertEquals(initialAuthType, credentials.getAuthenticationType()); + assertTrue(credentials.hasRequestMetadata()); + assertTrue(credentials.hasRequestMetadataOnly()); + assertEquals(initialMetadata, credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + + credentials.refresh(); + + verify(initialScopedCredentials, times(1)).refresh(); + + credentials.updateCredentials(updatedCredentials); + + assertEquals(updatedAuthType, credentials.getAuthenticationType()); + assertFalse(credentials.hasRequestMetadata()); + assertFalse(credentials.hasRequestMetadataOnly()); + assertSame(updatedMetadata, credentials.getRequestMetadata(URI.create("https://example.com"))); + + credentials.refresh(); + + verify(updatedScopedCredentials, times(1)).refresh(); + } + + private void setupInitialCredentials() throws IOException { + when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); + when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); + when(initialScopedCredentials.getRequestMetadata(any(URI.class))) + .thenReturn(initialMetadata); + when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); + when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); + } + + private void setupUpdatedCredentials() throws IOException { + when(updatedCredentials.createScoped(scopes)).thenReturn(updatedScopedCredentials); + when(updatedScopedCredentials.getAuthenticationType()).thenReturn(updatedAuthType); + when(updatedScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(updatedMetadata); + when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); + when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); + } +} \ No newline at end of file From 02a00ac97417bccc4cd2bd6d4b552acaff40b489 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 15:02:54 +0000 Subject: [PATCH 02/28] chore: generate libraries at Mon Mar 2 15:00:10 UTC 2026 --- .../connection/MutableCredentials.java | 82 +++++++++---------- .../connection/ITMutableCredentialsTest.java | 4 +- .../connection/MutableCredentialsTest.java | 13 +-- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 82326bdac6..8ada4f2ce0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -17,7 +17,6 @@ import com.google.auth.Credentials; import com.google.auth.oauth2.ServiceAccountCredentials; - import java.io.IOException; import java.net.URI; import java.util.List; @@ -31,56 +30,55 @@ * underlying service account credentials for a running Spanner Client. * *

All operations inherited from {@link Credentials} are forwarded to the current delegate, - * including request metadata retrieval and token refresh. Calling - * {@link #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped + * including request metadata retrieval and token refresh. Calling {@link + * #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped * credentials instance created from the same scopes that were provided when this object was * constructed. */ public class MutableCredentials extends Credentials { - ServiceAccountCredentials delegate; - List scopes; + ServiceAccountCredentials delegate; + List scopes; - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this. scopes = scopes; - delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); - } + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this.scopes = scopes; + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } - /** - * Replaces the current delegate with a newly scoped credentials instance. - * - *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were - * supplied when this {@link MutableCredentials} instance was created. - * - * @param credentials the new base service account credentials to scope and use for client - * authorization. - */ - public void updateCredentials(ServiceAccountCredentials credentials) { - delegate =(ServiceAccountCredentials) credentials.createScoped(scopes); - } + /** + * Replaces the current delegate with a newly scoped credentials instance. + * + *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were + * supplied when this {@link MutableCredentials} instance was created. + * + * @param credentials the new base service account credentials to scope and use for client + * authorization. + */ + public void updateCredentials(ServiceAccountCredentials credentials) { + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } - @Override - public String getAuthenticationType() { - return delegate.getAuthenticationType(); - } + @Override + public String getAuthenticationType() { + return delegate.getAuthenticationType(); + } - @Override - public Map> getRequestMetadata(URI uri) throws IOException { - return delegate.getRequestMetadata(uri); - } + @Override + public Map> getRequestMetadata(URI uri) throws IOException { + return delegate.getRequestMetadata(uri); + } - @Override - public boolean hasRequestMetadata() { - return delegate.hasRequestMetadata(); - } + @Override + public boolean hasRequestMetadata() { + return delegate.hasRequestMetadata(); + } - @Override - public boolean hasRequestMetadataOnly() { - return delegate.hasRequestMetadataOnly(); - } + @Override + public boolean hasRequestMetadataOnly() { + return delegate.hasRequestMetadataOnly(); + } - @Override - public void refresh() throws IOException { - delegate.refresh(); - } + @Override + public void refresh() throws IOException { + delegate.refresh(); + } } - diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 8cd37a3962..4064151e13 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -54,7 +54,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( - "This test requires service account credentials", + "This test requires service account credentials", credentialsFromFile instanceof ServiceAccountCredentials); ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; @@ -91,4 +91,4 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I closeSpanner(); } } -} \ No newline at end of file +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 8447802bb4..e609cd0d26 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -45,9 +45,9 @@ public class MutableCredentialsTest { ServiceAccountCredentials updatedScopedCredentials = mock(ServiceAccountCredentials.class); List scopes = Arrays.asList("scope-a", "scope-b"); Map> initialMetadata = - Collections.singletonMap("Authorization", Collections.singletonList("v1")); + Collections.singletonMap("Authorization", Collections.singletonList("v1")); Map> updatedMetadata = - Collections.singletonMap("Authorization", Collections.singletonList("v2")); + Collections.singletonMap("Authorization", Collections.singletonList("v2")); String initialAuthType = "auth-1"; String updatedAuthType = "auth-2"; @@ -61,7 +61,9 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { assertEquals(initialAuthType, credentials.getAuthenticationType()); assertTrue(credentials.hasRequestMetadata()); assertTrue(credentials.hasRequestMetadataOnly()); - assertEquals(initialMetadata, credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + assertEquals( + initialMetadata, + credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); credentials.refresh(); @@ -82,8 +84,7 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { private void setupInitialCredentials() throws IOException { when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); - when(initialScopedCredentials.getRequestMetadata(any(URI.class))) - .thenReturn(initialMetadata); + when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); } @@ -95,4 +96,4 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } -} \ No newline at end of file +} From bd89a334157d945cb1b909d879be42e58b2217f5 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:13:57 -0500 Subject: [PATCH 03/28] Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../cloud/spanner/connection/MutableCredentials.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 8ada4f2ce0..d47299fc20 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -40,10 +40,10 @@ public class MutableCredentials extends Credentials { List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = scopes; - delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); - } - + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + } /** * Replaces the current delegate with a newly scoped credentials instance. * From e871f0f4c4ca21583e433742750a1c1e49cf9d9a Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 10:17:47 -0500 Subject: [PATCH 04/28] fixed comp issue --- .../cloud/spanner/connection/MutableCredentials.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index d47299fc20..f0f62209ca 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -39,11 +39,11 @@ public class MutableCredentials extends Credentials { ServiceAccountCredentials delegate; List scopes; + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); - delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); - } + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + } /** * Replaces the current delegate with a newly scoped credentials instance. * From b802f0af43adc05bb262fb473d9d8ec1b5220add Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 10:19:45 -0500 Subject: [PATCH 05/28] suggestions from gemini review + lint fixes --- .../spanner/connection/MutableCredentials.java | 8 ++++---- .../connection/ITMutableCredentialsTest.java | 1 + .../spanner/connection/MutableCredentialsTest.java | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index f0f62209ca..9d014e50b5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -26,8 +26,8 @@ * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped * {@link ServiceAccountCredentials} instance. * - *

This class is intended for scenarios where an application needs to rotate or replace the - * underlying service account credentials for a running Spanner Client. + *

This class is intended for scenarios where an application needs to replace the underlying + * service account credentials for a long running Spanner Client. * *

All operations inherited from {@link Credentials} are forwarded to the current delegate, * including request metadata retrieval and token refresh. Calling {@link @@ -36,8 +36,8 @@ * constructed. */ public class MutableCredentials extends Credentials { - ServiceAccountCredentials delegate; - List scopes; + private volatile ServiceAccountCredentials delegate; + private final List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 4064151e13..7661d9324a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -92,3 +92,4 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I } } } + diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index e609cd0d26..6adb7a48c2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -52,9 +52,8 @@ public class MutableCredentialsTest { String updatedAuthType = "auth-2"; @Test - public void testCreateMutableCredentialsAndUpdate() throws IOException { + public void testCreateMutableCredentials() throws IOException { setupInitialCredentials(); - setupUpdatedCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); @@ -68,6 +67,16 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { credentials.refresh(); verify(initialScopedCredentials, times(1)).refresh(); + } + + @Test + public void testUpdateMutableCredentials() throws IOException { + setupInitialCredentials(); + setupUpdatedCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + + assertEquals(initialAuthType, credentials.getAuthenticationType()); credentials.updateCredentials(updatedCredentials); @@ -97,3 +106,4 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } } + From 40abd6dfed9d5bd9729f192ea1ecdfd5a36bd2b9 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 15:23:05 +0000 Subject: [PATCH 06/28] chore: generate libraries at Mon Mar 2 15:20:24 UTC 2026 --- .../google/cloud/spanner/connection/MutableCredentials.java | 6 +++--- .../cloud/spanner/connection/ITMutableCredentialsTest.java | 1 - .../cloud/spanner/connection/MutableCredentialsTest.java | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 9d014e50b5..78d250a133 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -39,11 +39,11 @@ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; private final List scopes; - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); - delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); } + /** * Replaces the current delegate with a newly scoped credentials instance. * diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 7661d9324a..4064151e13 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -92,4 +92,3 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I } } } - diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 6adb7a48c2..05bf943115 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -106,4 +106,3 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } } - From e5654af42cd3bc728dd7b9b587f40b36eec3f263 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 11:57:35 -0500 Subject: [PATCH 07/28] test IT test --- .../{ => it}/ITMutableCredentialsTest.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/{ => it}/ITMutableCredentialsTest.java (63%) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java similarity index 63% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 4064151e13..9d121334ae 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -14,26 +14,30 @@ * limitations under the License. */ -package com.google.cloud.spanner.connection; +package com.google.cloud.spanner.connection.it; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.ErrorCode; -import com.google.cloud.spanner.ResultSet; -import com.google.cloud.spanner.SerialIntegrationTest; -import com.google.cloud.spanner.SpannerException; -import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.*; + import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; + +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.Connection; +import com.google.cloud.spanner.connection.ConnectionOptions; +import com.google.cloud.spanner.connection.ITAbstractSpannerTest; +import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -44,13 +48,15 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String INVALID_KEY_FILE = ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + /*private static final String VALID_KEY_FILE = + ITMutableCredentialsTest.class.getResource("test-key-cloud-storage.json").getPath(); +*/ @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - assumeTrue("This test requires a service account key file", hasValidKeyFile()); GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -66,23 +72,19 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); - StringBuilder uri = - extractConnectionUrl(getTestEnv().getTestHelper().getOptions(), getDatabase()); - ConnectionOptions options = - ConnectionOptions.newBuilder() - .setUri(uri.toString()) + SpannerOptions options = + SpannerOptions.newBuilder() .setCredentials(mutableCredentials) .build(); - try (Connection connection = options.getConnection()) { - try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 1"))) { - assertTrue(rs.next()); - } - - mutableCredentials.updateCredentials(invalidCredentials); - - try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 2"))) { - rs.next(); + try (Spanner spanner = options.getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + String dbName = DatabaseName.of(GceTestEnvConfig.GCE_PROJECT_ID, getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST").toString(); + Database database = databaseAdminClient.getDatabase(dbName); + assertNotNull(database); + try { + mutableCredentials.updateCredentials(invalidCredentials); + databaseAdminClient.getDatabase(dbName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); From 28bb07a3a8c4243d3d5524f8fac3d0c184491c04 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 12:07:10 -0500 Subject: [PATCH 08/28] remove commented out code --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 9d121334ae..44f459a280 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -48,9 +48,6 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String INVALID_KEY_FILE = ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); - /*private static final String VALID_KEY_FILE = - ITMutableCredentialsTest.class.getResource("test-key-cloud-storage.json").getPath(); -*/ @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { From 1a302e8cf778ef0d6da54c9bca222c9d1fc69c86 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 17:11:22 +0000 Subject: [PATCH 09/28] chore: generate libraries at Mon Mar 2 17:08:19 UTC 2026 --- .../it/ITMutableCredentialsTest.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 44f459a280..813471b79a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -22,22 +22,17 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; - +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.ITAbstractSpannerTest; +import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.Connection; -import com.google.cloud.spanner.connection.ConnectionOptions; -import com.google.cloud.spanner.connection.ITAbstractSpannerTest; -import com.google.cloud.spanner.connection.MutableCredentials; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -53,7 +48,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { + try (InputStream stream = + Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -69,14 +65,16 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); - SpannerOptions options = - SpannerOptions.newBuilder() - .setCredentials(mutableCredentials) - .build(); + SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - String dbName = DatabaseName.of(GceTestEnvConfig.GCE_PROJECT_ID, getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST").toString(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + String dbName = + DatabaseName.of( + GceTestEnvConfig.GCE_PROJECT_ID, + getTestEnv().getTestHelper().getInstanceId().getInstance(), + "TEST") + .toString(); Database database = databaseAdminClient.getDatabase(dbName); assertNotNull(database); try { From 74995fecd0ad1e1f182113611e3643df663c85ca Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 15:09:47 -0500 Subject: [PATCH 10/28] added missing override methods --- .../connection/MutableCredentials.java | 25 +++++++- .../connection/MutableCredentialsTest.java | 62 ++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 78d250a133..b37323eaaf 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -15,12 +15,16 @@ */ package com.google.cloud.spanner.connection; +import com.google.auth.CredentialTypeForMetrics; import com.google.auth.Credentials; +import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.net.URI; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped @@ -40,7 +44,11 @@ public class MutableCredentials extends Credentials { private final List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); + if (scopes != null) { + this.scopes = new java.util.ArrayList<>(scopes); + } else { + this.scopes = Collections.emptyList(); + } delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); } @@ -81,4 +89,19 @@ public boolean hasRequestMetadataOnly() { public void refresh() throws IOException { delegate.refresh(); } + + @Override + public void getRequestMetadata(URI uri, Executor executor, RequestMetadataCallback callback) { + delegate.getRequestMetadata(uri, executor, callback); + } + + @Override + public String getUniverseDomain() throws IOException { + return delegate.getUniverseDomain(); + } + + @Override + public CredentialTypeForMetrics getMetricsCredentialType() { + return delegate.getMetricsCredentialType(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 05bf943115..e4bf0820a2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -26,6 +26,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.auth.CredentialTypeForMetrics; +import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.net.URI; @@ -33,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,22 +53,29 @@ public class MutableCredentialsTest { Collections.singletonMap("Authorization", Collections.singletonList("v2")); String initialAuthType = "auth-1"; String updatedAuthType = "auth-2"; + String initialUniverseDomain = "googleapis.com"; + String updatedUniverseDomain = "abc.goog"; + CredentialTypeForMetrics initialMetricsCredentialType = + CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_JWT; + CredentialTypeForMetrics updatedMetricsCredentialType = + CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_AT; @Test public void testCreateMutableCredentials() throws IOException { setupInitialCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + URI testUri = URI.create("https://spanner.googleapis.com"); + Executor executor = mock(Executor.class); + RequestMetadataCallback callback = mock(RequestMetadataCallback.class); - assertEquals(initialAuthType, credentials.getAuthenticationType()); - assertTrue(credentials.hasRequestMetadata()); - assertTrue(credentials.hasRequestMetadataOnly()); - assertEquals( - initialMetadata, - credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + validateInitialDelegatedCredentialsAreSet(credentials, testUri); + + credentials.getRequestMetadata(testUri, executor, callback); credentials.refresh(); + verify(initialScopedCredentials, times(1)).getRequestMetadata(testUri, executor, callback); verify(initialScopedCredentials, times(1)).refresh(); } @@ -75,25 +85,58 @@ public void testUpdateMutableCredentials() throws IOException { setupUpdatedCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + URI testUri = URI.create("https://example.com"); + Executor executor = mock(Executor.class); + RequestMetadataCallback callback = mock(RequestMetadataCallback.class); - assertEquals(initialAuthType, credentials.getAuthenticationType()); + validateInitialDelegatedCredentialsAreSet(credentials, testUri); credentials.updateCredentials(updatedCredentials); assertEquals(updatedAuthType, credentials.getAuthenticationType()); assertFalse(credentials.hasRequestMetadata()); assertFalse(credentials.hasRequestMetadataOnly()); - assertSame(updatedMetadata, credentials.getRequestMetadata(URI.create("https://example.com"))); + assertSame(updatedMetadata, credentials.getRequestMetadata(testUri)); + assertEquals(updatedUniverseDomain, credentials.getUniverseDomain()); + assertEquals(updatedMetricsCredentialType, credentials.getMetricsCredentialType()); + + credentials.getRequestMetadata(testUri, executor, callback); credentials.refresh(); + verify(updatedScopedCredentials, times(1)).getRequestMetadata(testUri, executor, callback); verify(updatedScopedCredentials, times(1)).refresh(); } + @Test + public void testCreateMutableCredentialsNullScopes() throws IOException { + setupInitialCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, null); + URI testUri = URI.create("https://spanner.googleapis.com"); + + validateInitialDelegatedCredentialsAreSet(credentials, testUri); + } + + private void validateInitialDelegatedCredentialsAreSet( + MutableCredentials credentials, URI testUri) throws IOException { + assertEquals(initialAuthType, credentials.getAuthenticationType()); + assertTrue(credentials.hasRequestMetadata()); + assertTrue(credentials.hasRequestMetadataOnly()); + assertEquals(initialMetadata, credentials.getRequestMetadata(testUri)); + assertEquals(initialUniverseDomain, credentials.getUniverseDomain()); + assertEquals(initialMetricsCredentialType, credentials.getMetricsCredentialType()); + } + private void setupInitialCredentials() throws IOException { when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); + when(initialCredentials.createScoped(Collections.emptyList())) + .thenReturn(initialScopedCredentials); when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); + when(initialScopedCredentials.getUniverseDomain()).thenReturn(initialUniverseDomain); + when(initialScopedCredentials.getMetricsCredentialType()) + .thenReturn(initialMetricsCredentialType); when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); } @@ -102,6 +145,9 @@ private void setupUpdatedCredentials() throws IOException { when(updatedCredentials.createScoped(scopes)).thenReturn(updatedScopedCredentials); when(updatedScopedCredentials.getAuthenticationType()).thenReturn(updatedAuthType); when(updatedScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(updatedMetadata); + when(updatedScopedCredentials.getUniverseDomain()).thenReturn(updatedUniverseDomain); + when(updatedScopedCredentials.getMetricsCredentialType()) + .thenReturn(updatedMetricsCredentialType); when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } From 61468f0ca37e724dd7d64d2f5f9bfe83a5b51907 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 16:06:05 -0500 Subject: [PATCH 11/28] attempt to fix IT tests --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 813471b79a..3df5689ab2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -41,8 +41,8 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { - private static final String INVALID_KEY_FILE = - ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + private static final String INVALID_KEY_RESOURCE = + "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { @@ -58,7 +58,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_FILE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From fc8c68278cd19c9036e5d2454c283372f5998b63 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 21:09:33 +0000 Subject: [PATCH 12/28] chore: generate libraries at Tue Mar 3 21:06:51 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3df5689ab2..1e078c7d53 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -58,7 +58,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 2cf5b89cacb6d96ca94aafe3fc72c5fcaeedbcc4 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 16:24:33 -0500 Subject: [PATCH 13/28] try to use default key file --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 1e078c7d53..b10cb71347 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -49,7 +49,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I GoogleCredentials credentialsFromFile; try (InputStream stream = - Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { + Files.newInputStream(Paths.get(getKeyFile()))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( From 1751af283b60f122de93a07b3c340d546c6f954d Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 21:30:08 +0000 Subject: [PATCH 14/28] chore: generate libraries at Tue Mar 3 21:25:30 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index b10cb71347..3014fcd27f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -48,8 +48,7 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = - Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( From 22a9ba714da4140d68725adb5592ceb3d077c77a Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 17:12:46 -0500 Subject: [PATCH 15/28] try to use hardcoded service account file --- .../spanner/connection/it/ITMutableCredentialsTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3014fcd27f..3aefff5448 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -41,6 +41,9 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { + private static final String VALID_KEY_RESOURCE = + "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + private static final String INVALID_KEY_RESOURCE = "/com/google/cloud/spanner/connection/test-key.json"; @@ -48,7 +51,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = + Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -57,8 +61,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 80b67bf2a23c44ead8b88aad87ee0b076313ae89 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 22:16:01 +0000 Subject: [PATCH 16/28] chore: generate libraries at Tue Mar 3 22:13:15 UTC 2026 --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3aefff5448..58568551ad 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -42,7 +42,7 @@ @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; private static final String INVALID_KEY_RESOURCE = "/com/google/cloud/spanner/connection/test-key.json"; @@ -51,8 +51,7 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = - Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { + try (InputStream stream = Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -61,7 +60,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From b0c151469dcfedc9dad821ade5798cdb8e65b68d Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 10:40:13 -0500 Subject: [PATCH 17/28] change to use resource as stream # Conflicts: # google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 58568551ad..1d06db226a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -29,8 +29,6 @@ import com.google.spanner.admin.database.v1.DatabaseName; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -51,7 +49,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { + assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -60,7 +59,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 910eb82dba2c7b84ed6dec30df5761102acf4506 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 18:00:54 +0000 Subject: [PATCH 18/28] chore: generate libraries at Wed Mar 4 17:58:06 UTC 2026 --- .../spanner/connection/it/ITMutableCredentialsTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 1d06db226a..8fc04705ed 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -49,7 +49,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); credentialsFromFile = GoogleCredentials.fromStream(stream); } @@ -59,7 +60,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 816dcfce5feefa7dac63c88d0c3c164c49fe4eab Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:06:36 -0500 Subject: [PATCH 19/28] change to use correct project Id --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 8fc04705ed..7f6cfa57d2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -75,7 +75,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { String dbName = DatabaseName.of( - GceTestEnvConfig.GCE_PROJECT_ID, + getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); From d3dab3407e27ded2aa2f50e57e9109c6f73288b1 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:26:51 -0500 Subject: [PATCH 20/28] change to use new api for test --- .../it/ITMutableCredentialsTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 7f6cfa57d2..75383efe68 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -31,6 +31,8 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; + +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -73,17 +75,28 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - String dbName = + /* String dbName = DatabaseName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); - Database database = databaseAdminClient.getDatabase(dbName); - assertNotNull(database); + Database database = databaseAdminClient.getDatabase(dbName);*/ + InstanceName instanceName = InstanceName.of(getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getProject()); + DatabaseAdminClient.ListDatabasesPagedResponse response = + databaseAdminClient.listDatabases(instanceName); + + boolean databaseFound = false; + for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { + for (Database database : page.iterateAll()) { + System.out.println("\t" + database.getName()); + databaseFound = true; + } + } + assertTrue(databaseFound); try { mutableCredentials.updateCredentials(invalidCredentials); - databaseAdminClient.getDatabase(dbName); + databaseAdminClient.listDatabases(instanceName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); From 9739af225751ffd9b08b1db472ec1b8021136de2 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 18:30:09 +0000 Subject: [PATCH 21/28] chore: generate libraries at Wed Mar 4 18:27:30 UTC 2026 --- .../connection/it/ITMutableCredentialsTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 75383efe68..553d7f9327 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -26,13 +26,11 @@ import com.google.cloud.spanner.connection.ITAbstractSpannerTest; import com.google.cloud.spanner.connection.MutableCredentials; import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.InstanceName; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; - -import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -75,22 +73,25 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - /* String dbName = + /* String dbName = DatabaseName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); Database database = databaseAdminClient.getDatabase(dbName);*/ - InstanceName instanceName = InstanceName.of(getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getProject()); + InstanceName instanceName = + InstanceName.of( + getTestEnv().getTestHelper().getInstanceId().getProject(), + getTestEnv().getTestHelper().getInstanceId().getProject()); DatabaseAdminClient.ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(instanceName); + databaseAdminClient.listDatabases(instanceName); boolean databaseFound = false; for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - databaseFound = true; + System.out.println("\t" + database.getName()); + databaseFound = true; } } assertTrue(databaseFound); From 8c690d26ed67be21f366d53754773fa7001254a2 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:45:26 -0500 Subject: [PATCH 22/28] fix instance name --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 553d7f9327..8100e051f3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -83,7 +83,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I InstanceName instanceName = InstanceName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getProject()); + getTestEnv().getTestHelper().getInstanceId().getInstance()); DatabaseAdminClient.ListDatabasesPagedResponse response = databaseAdminClient.listDatabases(instanceName); From 2d70ec5d466421de91d8bdd28bd5be19019c4b74 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:58:48 -0500 Subject: [PATCH 23/28] add invalid test key for IT tests --- .../connection/it/ITMutableCredentialsTest.java | 12 +++++++++--- .../cloud/spanner/connection/invalid-test-key.json | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 8100e051f3..c7a98f9d52 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -40,10 +40,10 @@ @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + "/com/google/cloud/spanner/connection/test-key.json"; private static final String INVALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key.json"; + "/com/google/cloud/spanner/connection/invalid-test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { @@ -97,7 +97,13 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I assertTrue(databaseFound); try { mutableCredentials.updateCredentials(invalidCredentials); - databaseAdminClient.listDatabases(instanceName); + DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = + databaseAdminClient.listDatabases(instanceName); + for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { + for (Database database : page.iterateAll()) { + System.out.println("\t" + database.getName()); + } + } fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json new file mode 100644 index 0000000000..424df9b36e --- /dev/null +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "ldetmer-sanbox", + "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", + "client_email": "test-spanner@ldetmer-sanbox.iam.gserviceaccount.com", + "client_id": "106624917120625257013", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-spanner%40ldetmer-sanbox.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From 917cb2db2a40673d35262a2e351cded8970fab5d Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 14:00:06 -0500 Subject: [PATCH 24/28] change test key to be invalid --- .../com/google/cloud/spanner/connection/invalid-test-key.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json index 424df9b36e..a217110775 100644 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json @@ -1,9 +1,9 @@ { "type": "service_account", - "project_id": "ldetmer-sanbox", + "project_id": "invalid", "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", - "client_email": "test-spanner@ldetmer-sanbox.iam.gserviceaccount.com", + "client_email": "test-spanner@invalid.iam.gserviceaccount.com", "client_id": "106624917120625257013", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", From fa7ea61405d1c2d51768afaa9d19f8414f9538e0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 19:03:52 +0000 Subject: [PATCH 25/28] chore: generate libraries at Wed Mar 4 19:01:08 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index c7a98f9d52..a3abf696ea 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -98,7 +98,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try { mutableCredentials.updateCredentials(invalidCredentials); DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = - databaseAdminClient.listDatabases(instanceName); + databaseAdminClient.listDatabases(instanceName); for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { for (Database database : page.iterateAll()) { System.out.println("\t" + database.getName()); From 1d41b10a8b0e38388a736162e6571b139073437b Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:48:09 -0500 Subject: [PATCH 26/28] working IT test --- .../it/ITMutableCredentialsTest.java | 98 ++++++++----------- .../spanner/connection/invalid-test-key.json | 13 --- .../test-key-missing-permissions.json | 13 +++ 3 files changed, 54 insertions(+), 70 deletions(-) delete mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json create mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index a3abf696ea..c6db28fe0f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -17,19 +17,17 @@ package com.google.cloud.spanner.connection.it; import static org.junit.Assert.*; -import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.ITAbstractSpannerTest; import com.google.cloud.spanner.connection.MutableCredentials; import com.google.spanner.admin.database.v1.Database; import com.google.spanner.admin.database.v1.InstanceName; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -38,78 +36,64 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) -public class ITMutableCredentialsTest extends ITAbstractSpannerTest { - private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key.json"; +public class ITMutableCredentialsTest { + private static final String MISSING_PERM_KEY = + "/com/google/cloud/spanner/connection/test-key-missing-permissions.json"; - private static final String INVALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/invalid-test-key.json"; + private static final String INVALID_KEY = "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - GoogleCredentials credentialsFromFile; + GoogleCredentials missingPermissionCredentials; try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { - assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); - credentialsFromFile = GoogleCredentials.fromStream(stream); + ITMutableCredentialsTest.class.getResourceAsStream(MISSING_PERM_KEY)) { + missingPermissionCredentials = GoogleCredentials.fromStream(stream); } - assumeTrue( - "This test requires service account credentials", - credentialsFromFile instanceof ServiceAccountCredentials); - - ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { - assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY)) { invalidCredentials = ServiceAccountCredentials.fromStream(stream); } - - List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); - MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); + List scopes = + Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"); + // create MutableCredentials first with missing permissions + MutableCredentials mutableCredentials = + new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials, scopes); SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); - try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - /* String dbName = - DatabaseName.of( - getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getInstance(), - "TEST") - .toString(); - Database database = databaseAdminClient.getDatabase(dbName);*/ - InstanceName instanceName = - InstanceName.of( - getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getInstance()); - DatabaseAdminClient.ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(instanceName); - - boolean databaseFound = false; - for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { - for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - databaseFound = true; - } + String project = "gcloud-devel"; + String instance = "java-client-integration-tests"; + try { + listDatabases(databaseAdminClient, project, instance); + } catch (Exception e) { + // specifically validate the permission denied error message + assertTrue(e.getMessage().contains("PERMISSION_DENIED")); + assertFalse(e.getMessage().contains("UNAUTHENTICATED")); } - assertTrue(databaseFound); + + // update mutableCredentials now to use an invalid credential + mutableCredentials.updateCredentials(invalidCredentials); try { - mutableCredentials.updateCredentials(invalidCredentials); - DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = - databaseAdminClient.listDatabases(instanceName); - for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { - for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - } - } + listDatabases(databaseAdminClient, project, instance); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); - } catch (SpannerException e) { - assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); + } catch (Exception e) { + assertTrue(e.getMessage().contains("UNAUTHENTICATED")); + assertFalse(e.getMessage().contains("PERMISSION_DENIED")); + } + } + } + + private static void listDatabases( + DatabaseAdminClient databaseAdminClient, String projectId, String instanceId) { + DatabaseAdminClient.ListDatabasesPagedResponse response = + databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId)); + + for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { + for (Database database : page.iterateAll()) { + // no-op } - } finally { - closeSpanner(); } } } diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json deleted file mode 100644 index a217110775..0000000000 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "invalid", - "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", - "client_email": "test-spanner@invalid.iam.gserviceaccount.com", - "client_id": "106624917120625257013", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-spanner%40ldetmer-sanbox.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json new file mode 100644 index 0000000000..348e769f6f --- /dev/null +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "ldetmer-sanbox", + "private_key_id": "1f9be0fd206d51e759ab8577c32301333dda9103", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgwaPePW0yK6Wg\nm0n0PgrmJbwbf0HgFQ9E5I5e+rZ+hl79pCxCkXcTVH6HsIgh+Lp1tyCTExjsE0cN\nDVRy9YD/BR4wKsxOhz5UKwmRhOg127EGvqMAwomEGOIyOuS+AMmdy710B/iBjc01\nLYTzNe1ldZ5SK69AJer9g1+mPn2H7AbZGQ8/ThKvzEAErmgd7PMATRwg6sAc5n6O\nIvcfQp4XApcYuCNH4KVmnXD3K9f0ySV5WKA14eHRIIxwjHagFvHaiMvU4/HXqCao\nClVoNmmoraUBfaDGj4JGMvj1JjTyz4VB0KGXxYTJJrViYhkKZs4zSo2k2qatPczn\nNHH6PkjLAgMBAAECggEAD+QWYPfjiOuQb1WWGgBqUYa+JmI4rHjwtm9D0wVTnTMv\nniwFu8MrMcuvPTVxupfVHyOOgwzo8zATEveBTrYlo7efQHVA3WsvMKZGVpVDZyNx\nqxy+y6agMHMjSGfk6mYwhclKS4eQviA3hRit0MBcWOhtruOgesmeNBnIy9PumOB+\nWVRp7uz8D/xaq55lFAShaH2DAEt316qetZW+LtNq473pPq9GlnFYsj+OPyWT84X2\nmeoWLxVwOpkx1RmmlAEOQCCK24H7GbZwsADiyHQ37cwQ/MfTs9qsib56TByVHSSb\nYD+lTMPT+5N/YY51AVq4op4kGPuVTHLE4D3uZduSEQKBgQDwmuSCjfvHr1LZxo/r\nVPR6+KiQC+o8qBzK0413D3rn+0pAWCcrkb9//PGwXxtRzjEodwbX9B+g8UGzkbfD\nH5scogl3Nd+3zUTsSp5D5IZUJsVv1lN2klv4y48zidey2qELOC8n6hPnrbOHfZqZ\nR22/o2/TeWxnWbmMUN2kx9r++QKBgQDvIyWePiJgFvlRLSLQQpZOiDV4z61Ixows\nDBrTeQyfAYG8gROA0LUS3zS4njA2Yr6xFj6M8rhUD9bLQ1+mGIJWi7ZI2cD+TDtH\ntdxTS7jBU8s26H2nisD8kvKpq61RxI1A2H7u+9gPzDweM0boBlERNqjyPUZhNbdD\n0+7AwmJC4wKBgB91kTVEzUvxr5qL7NtvUzwU8S1McYcW0BTxDkkn/AEDCVVacVyw\nBOL+NrfB57eNhz3sOjfYUp5fjSCmh+l6Y3Sd9zDgGW1V6JIgu4rTAYFVRHF4C5ew\nUVg5fXLWrh5TmcT2xquoXovnWVb45FLwVPg+rWtwL+1ffPRMyn42J3s5AoGAf6CR\ndigRLpl0THe7aczv7U/SwfyMrheRPfzj4FNtgftK43E8GHbK/Rx1RcbfUldXEKof\njhgIeozNhUQa60mPXmNIUQ8uakoDJV2RDj+OhleTUGW6kk2CfAptSlKeuNIe1Sn2\nbNOqV5wXxcJ2KGUepQI4HrjHNCB4A9I7TVMxICMCgYAPXO4/xTZJ/0Nmjd085yRo\nhDFBUTwWPHUTbUA1bBMd908F4RD0WnnLPzSC1hSxhhCGm119JGgusZfwL2Ey1nYh\n9B3b/EwArE/vC+Fl/tyILQR2G/D/f70dISuDut139cKEM8qBLJ2JRuYbKlEBPhGW\nw0x8SmTkNYepAG0SSaBu7g==\n-----END PRIVATE KEY-----\n", + "client_email": "test-mutable-credentials@ldetmer-sanbox.iam.gserviceaccount.com", + "client_id": "110488447517330409458", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-mutable-credentials%40ldetmer-sanbox.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From 037870676c4dbd998810f626a12515c2981af449 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:58:19 -0500 Subject: [PATCH 27/28] need to check error message on kokoro as its different then local --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index c6db28fe0f..83f5734131 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -69,6 +69,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I listDatabases(databaseAdminClient, project, instance); } catch (Exception e) { // specifically validate the permission denied error message + System.out.println("exception " + e.getMessage()); assertTrue(e.getMessage().contains("PERMISSION_DENIED")); assertFalse(e.getMessage().contains("UNAUTHENTICATED")); } From 9c4b12bc5d58fc3cbac78b054f8f2b6974dd00a3 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:58:50 -0500 Subject: [PATCH 28/28] need to check error message on kokoro as its different then local --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 83f5734131..8ab0e283ea 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -67,6 +67,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I String instance = "java-client-integration-tests"; try { listDatabases(databaseAdminClient, project, instance); + fail("Expected PERMISSION_DENIED"); } catch (Exception e) { // specifically validate the permission denied error message System.out.println("exception " + e.getMessage());