test(auth): add caching call-count assertions and fix mock expiration#13453
test(auth): add caching call-count assertions and fix mock expiration#13453westarle wants to merge 1 commit into
Conversation
… 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.
There was a problem hiding this comment.
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.
| 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]); | ||
| } |
There was a problem hiding this comment.
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());
}
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_usesCachedTokento enforce this.Adding this test exposed a long-standing bug in the test framework:
MockMetadataServerTransportwas erroneously returningexpires_inas 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
longto prevent any potential future overflows.