Skip to content

test(auth): add caching call-count assertions and fix mock expiration#13453

Open
westarle wants to merge 1 commit into
googleapis:mainfrom
westarle:fix-test-coverage-gap-compute-engine
Open

test(auth): add caching call-count assertions and fix mock expiration#13453
westarle wants to merge 1 commit into
googleapis:mainfrom
westarle:fix-test-coverage-gap-compute-engine

Conversation

@westarle

@westarle westarle commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Other client libraries (such as Rust and .NET) safely verify caching logic by asserting exactly one outbound HTTP request is made regardless of the number of credential calls. Added getRequestMetadata_multipleCalls_usesCachedToken to enforce this.

Adding this test exposed a long-standing bug in the test framework: MockMetadataServerTransport was erroneously returning expires_in as 3,600,000 (mistakenly assuming it was milliseconds instead of seconds). This caused a 32-bit integer overflow in the parser (expiresInSeconds * 1000 > 2.14B), which instantly expired the mock token and silently bypassed the cache during tests.

Fixed the mock to correctly return 3600 seconds, and defensively promoted the production code parser multiplication to a 64-bit long to prevent any potential future overflows.

… bug

Other client libraries (such as Rust and .NET) safely verify caching logic by asserting exactly one outbound HTTP request is made regardless of the number of credential calls. Added `getRequestMetadata_multipleCalls_usesCachedToken` to enforce this.

Adding this test exposed a long-standing bug in the test framework: `MockMetadataServerTransport` was erroneously returning `expires_in` as 3,600,000 (mistakenly assuming it was milliseconds instead of seconds). This caused a 32-bit integer overflow in the parser (`expiresInSeconds * 1000` > 2.14B), which instantly expired the mock token and silently bypassed the cache during tests.

Fixed the mock to correctly return 3600 seconds, and defensively promoted the production code parser multiplication to a 64-bit `long` to prevent any potential future overflows.
@westarle westarle requested review from a team as code owners June 12, 2026 16:35

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request prevents potential integer overflow when calculating token expiration times in ComputeEngineCredentials and UserCredentials by using a long literal (1000L). It also corrects the mock metadata server's expires_in response from milliseconds to seconds and adds a test to verify token caching. Feedback is provided to improve the test's thread safety by replacing the raw int[] array with an AtomicInteger.

Comment on lines +423 to +448
void getRequestMetadata_multipleCalls_usesCachedToken() throws IOException {
final int[] requestCount = new int[1];
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
transportFactory.transport =
new MockMetadataServerTransport(SCOPE_TO_ACCESS_TOKEN_MAP) {
@Override
public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
if (url.startsWith(ComputeEngineCredentials.getTokenServerEncodedUrl())) {
requestCount[0]++;
}
return super.buildRequest(method, url);
}
};
transportFactory.transport.setServiceAccountEmail(SA_CLIENT_EMAIL);

ComputeEngineCredentials credentials =
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();

Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
assertEquals(1, requestCount[0]);

Map<String, List<String>> metadata2 = credentials.getRequestMetadata(CALL_URI);
TestUtils.assertContainsBearerToken(metadata2, ACCESS_TOKEN);
assertEquals(1, requestCount[0]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a raw int[] array to track request counts across anonymous class boundaries is not thread-safe and can lead to race conditions if the transport is accessed concurrently. It is highly recommended to use AtomicInteger instead, which provides thread-safe atomic operations and cleaner syntax.

  void getRequestMetadata_multipleCalls_usesCachedToken() throws IOException {
    final java.util.concurrent.atomic.AtomicInteger requestCount = new java.util.concurrent.atomic.AtomicInteger(0);
    MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
    transportFactory.transport =
        new MockMetadataServerTransport(SCOPE_TO_ACCESS_TOKEN_MAP) {
          @Override
          public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
            if (url.startsWith(ComputeEngineCredentials.getTokenServerEncodedUrl())) {
              requestCount.incrementAndGet();
            }
            return super.buildRequest(method, url);
          }
        };
    transportFactory.transport.setServiceAccountEmail(SA_CLIENT_EMAIL);

    ComputeEngineCredentials credentials =
        ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();

    Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
    TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
    assertEquals(1, requestCount.get());

    Map<String, List<String>> metadata2 = credentials.getRequestMetadata(CALL_URI);
    TestUtils.assertContainsBearerToken(metadata2, ACCESS_TOKEN);
    assertEquals(1, requestCount.get());
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant