From b24a0fce8da59fee5bfe24f35b3e615ab4b91485 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Tue, 3 Jun 2025 17:37:45 +0000 Subject: [PATCH 01/26] Change LocalDateTime to Instant --- .../databricks/sdk/core/CliTokenSource.java | 32 +++++++++++++---- .../oauth/DatabricksOAuthTokenSource.java | 4 +-- .../sdk/core/oauth/EndpointTokenSource.java | 4 +-- .../sdk/core/oauth/OidcTokenSource.java | 4 +-- .../core/oauth/RefreshableTokenSource.java | 5 ++- .../com/databricks/sdk/core/oauth/Token.java | 22 ++++++------ .../core/AzureCliCredentialsProviderTest.java | 3 +- .../sdk/core/CliTokenSourceTest.java | 35 +++++++++++++++---- .../sdk/core/DatabricksConfigTest.java | 3 +- .../core/oauth/DataPlaneTokenSourceTest.java | 14 ++++---- .../core/oauth/EndpointTokenSourceTest.java | 4 +-- ...xternalBrowserCredentialsProviderTest.java | 16 ++++----- .../sdk/core/oauth/FileTokenCacheTest.java | 14 ++++---- .../core/oauth/OAuthHeaderFactoryTest.java | 6 ++-- .../TokenSourceCredentialsProviderTest.java | 4 +-- .../databricks/sdk/core/oauth/TokenTest.java | 15 ++++---- 16 files changed, 109 insertions(+), 76 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 8a7328904..3b3845e51 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -8,7 +8,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Arrays; @@ -36,21 +38,39 @@ public CliTokenSource( this.env = env; } - static LocalDateTime parseExpiry(String expiry) { + /** + * Parses an expiry time string and returns the corresponding {@link Instant}. + * + *

The expiry time string is verified to always be in UTC. Any time zone or offset information + * present in the input is ignored, and the value is parsed as a UTC time. + * + *

The method attempts to parse the input using several common date-time formats, including + * ISO-8601 and patterns with varying sub-second precision. + * + * @param expiry the expiry time string to parse, which must represent a UTC time + * @return the parsed {@link Instant} representing the expiry time in UTC + * @throws DateTimeParseException if the input string cannot be parsed as a valid date-time + */ + static Instant parseExpiry(String expiry) { + DateTimeParseException lastException = null; + try { + return Instant.parse(expiry); + } catch (DateTimeParseException e) { + lastException = e; + } + String multiplePrecisionPattern = "[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]"; List datePatterns = Arrays.asList( "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss." + multiplePrecisionPattern, - "yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "XXX", - "yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "'Z'"); - DateTimeParseException lastException = null; + "yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "XXX"); for (String pattern : datePatterns) { try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); LocalDateTime dateTime = LocalDateTime.parse(expiry, formatter); - return dateTime; + return dateTime.atZone(ZoneOffset.UTC).toInstant(); } catch (DateTimeParseException e) { lastException = e; } @@ -83,7 +103,7 @@ protected Token refresh() { String tokenType = jsonNode.get(tokenTypeField).asText(); String accessToken = jsonNode.get(accessTokenField).asText(); String expiry = jsonNode.get(expiryField).asText(); - LocalDateTime expiresOn = parseExpiry(expiry); + Instant expiresOn = parseExpiry(expiry); return new Token(accessToken, tokenType, expiresOn); } catch (DatabricksException e) { throw e; diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java index f16ae2aed..484e0712e 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java @@ -3,7 +3,7 @@ import com.databricks.sdk.core.DatabricksException; import com.databricks.sdk.core.http.HttpClient; import com.google.common.base.Strings; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -166,7 +166,7 @@ public Token refresh() { throw e; } - LocalDateTime expiry = LocalDateTime.now().plusSeconds(response.getExpiresIn()); + Instant expiry = Instant.now().plusSeconds(response.getExpiresIn()); return new Token( response.getAccessToken(), response.getTokenType(), response.getRefreshToken(), expiry); } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java index 3ca75c441..ed08f57d6 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java @@ -2,7 +2,7 @@ import com.databricks.sdk.core.DatabricksException; import com.databricks.sdk.core.http.HttpClient; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -87,7 +87,7 @@ protected Token refresh() { throw e; } - LocalDateTime expiry = LocalDateTime.now().plusSeconds(oauthResponse.getExpiresIn()); + Instant expiry = Instant.now().plusSeconds(oauthResponse.getExpiresIn()); return new Token( oauthResponse.getAccessToken(), oauthResponse.getTokenType(), diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OidcTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OidcTokenSource.java index 719544ebf..b15f55ded 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OidcTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/OidcTokenSource.java @@ -8,7 +8,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import java.io.IOException; -import java.time.LocalDateTime; +import java.time.Instant; /** * {@code OidcTokenSource} is responsible for obtaining OAuth tokens using the OpenID Connect (OIDC) @@ -77,7 +77,7 @@ protected Token refresh() { if (resp.getErrorCode() != null) { throw new IllegalArgumentException(resp.getErrorCode() + ": " + resp.getErrorSummary()); } - LocalDateTime expiry = LocalDateTime.now().plusSeconds(resp.getExpiresIn()); + Instant expiry = Instant.now().plusSeconds(resp.getExpiresIn()); return new Token(resp.getAccessToken(), resp.getTokenType(), resp.getRefreshToken(), expiry); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/RefreshableTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/RefreshableTokenSource.java index e93f91ae5..750aae967 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/RefreshableTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/RefreshableTokenSource.java @@ -5,8 +5,7 @@ import com.databricks.sdk.core.http.FormRequest; import com.databricks.sdk.core.http.HttpClient; import com.databricks.sdk.core.http.Request; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; +import java.time.Instant; import java.util.Base64; import java.util.Map; import org.apache.http.HttpHeaders; @@ -70,7 +69,7 @@ protected static Token retrieveToken( if (resp.getErrorCode() != null) { throw new IllegalArgumentException(resp.getErrorCode() + ": " + resp.getErrorSummary()); } - LocalDateTime expiry = LocalDateTime.now().plus(resp.getExpiresIn(), ChronoUnit.SECONDS); + Instant expiry = Instant.now().plusSeconds(resp.getExpiresIn()); return new Token(resp.getAccessToken(), resp.getTokenType(), resp.getRefreshToken(), expiry); } catch (Exception e) { throw new DatabricksException("Failed to refresh credentials: " + e.getMessage(), e); diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java index f0fd72f68..ac6fbc3ac 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java @@ -4,8 +4,7 @@ import com.databricks.sdk.core.utils.SystemClockSupplier; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; +import java.time.Instant; import java.util.Objects; public class Token { @@ -19,21 +18,20 @@ public class Token { * The expiry time of the token. * *

OAuth token responses include the duration of the lifetime of the access token. When the - * token is retrieved, this is converted to a LocalDateTime tracking the expiry time of the token - * with respect to the current clock. + * token is retrieved, this is converted to an Instant tracking the expiry time of the token with + * respect to the current clock. */ - @JsonProperty private LocalDateTime expiry; + @JsonProperty private Instant expiry; private final ClockSupplier clockSupplier; /** Constructor for non-refreshable tokens (e.g. M2M). */ - public Token(String accessToken, String tokenType, LocalDateTime expiry) { + public Token(String accessToken, String tokenType, Instant expiry) { this(accessToken, tokenType, null, expiry, new SystemClockSupplier()); } /** Constructor for non-refreshable tokens (e.g. M2M) with ClockSupplier */ - public Token( - String accessToken, String tokenType, LocalDateTime expiry, ClockSupplier clockSupplier) { + public Token(String accessToken, String tokenType, Instant expiry, ClockSupplier clockSupplier) { this(accessToken, tokenType, null, expiry, clockSupplier); } @@ -43,7 +41,7 @@ public Token( @JsonProperty("accessToken") String accessToken, @JsonProperty("tokenType") String tokenType, @JsonProperty("refreshToken") String refreshToken, - @JsonProperty("expiry") LocalDateTime expiry) { + @JsonProperty("expiry") Instant expiry) { this(accessToken, tokenType, refreshToken, expiry, new SystemClockSupplier()); } @@ -52,7 +50,7 @@ public Token( String accessToken, String tokenType, String refreshToken, - LocalDateTime expiry, + Instant expiry, ClockSupplier clockSupplier) { Objects.requireNonNull(accessToken, "accessToken must be defined"); Objects.requireNonNull(tokenType, "tokenType must be defined"); @@ -71,8 +69,8 @@ public boolean isExpired() { } // Azure Databricks rejects tokens that expire in 30 seconds or less, // so we refresh the token 40 seconds before it expires. - LocalDateTime potentiallyExpired = expiry.minus(40, ChronoUnit.SECONDS); - LocalDateTime now = LocalDateTime.now(clockSupplier.getClock()); + Instant potentiallyExpired = expiry.minusSeconds(40); + Instant now = Instant.now(clockSupplier.getClock()); return potentiallyExpired.isBefore(now); } diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/AzureCliCredentialsProviderTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/AzureCliCredentialsProviderTest.java index 0212b7652..4e8a57b06 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/AzureCliCredentialsProviderTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/AzureCliCredentialsProviderTest.java @@ -7,7 +7,6 @@ import com.databricks.sdk.core.oauth.Token; import com.databricks.sdk.core.oauth.TokenSource; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; @@ -25,7 +24,7 @@ class AzureCliCredentialsProviderTest { private static CliTokenSource mockTokenSource() { CliTokenSource tokenSource = Mockito.mock(CliTokenSource.class); Mockito.when(tokenSource.getToken()) - .thenReturn(new Token(TOKEN, TOKEN_TYPE, LocalDateTime.now())); + .thenReturn(new Token(TOKEN, TOKEN_TYPE, java.time.Instant.now())); return tokenSource; } diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 20e2f6095..abe609b01 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.time.LocalDateTime; +import java.time.Instant; import java.time.format.DateTimeParseException; import org.junit.jupiter.api.Test; @@ -10,20 +10,20 @@ public class CliTokenSourceTest { @Test public void testParseExpiryWithoutTruncate() { - LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z"); - assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612218), parsedDateTime); + Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z"); + assertEquals(Instant.parse("2023-07-17T09:02:22.330612218Z"), parsedInstant); } @Test public void testParseExpiryWithTruncate() { - LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221Z"); - assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612210), parsedDateTime); + Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221Z"); + assertEquals(Instant.parse("2023-07-17T09:02:22.330612210Z"), parsedInstant); } @Test public void testParseExpiryWithTruncateAndLessNanoSecondDigits() { - LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612Z"); - assertEquals(LocalDateTime.of(2023, 7, 17, 9, 2, 22, 330612000), parsedDateTime); + Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612Z"); + assertEquals(Instant.parse("2023-07-17T09:02:22.330612000Z"), parsedInstant); } @Test @@ -34,4 +34,25 @@ public void testParseExpiryWithMoreThanNineNanoSecondDigits() { assert (e.getMessage().contains("could not be parsed")); } } + + @Test + public void testParseExpiryWithSpaceFormat() { + Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22"); + assertEquals(Instant.parse("2023-07-17T09:02:22Z"), parsedInstant); + } + + @Test + public void testParseExpiryWithSpaceFormatAndMillis() { + Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22.123"); + assertEquals(Instant.parse("2023-07-17T09:02:22.123Z"), parsedInstant); + } + + @Test + public void testParseExpiryWithInvalidFormat() { + try { + CliTokenSource.parseExpiry("17-07-2023 09:02:22"); + } catch (DateTimeParseException e) { + assert (e.getMessage().contains("could not be parsed")); + } + } } diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java index 38b6fcd9c..b3ac333a3 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/DatabricksConfigTest.java @@ -12,7 +12,6 @@ import com.databricks.sdk.core.oauth.TokenSource; import com.databricks.sdk.core.utils.Environment; import java.io.IOException; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -232,7 +231,7 @@ public void testGetTokenSourceWithOAuth() { HttpClient httpClient = mock(HttpClient.class); TokenSource mockTokenSource = mock(TokenSource.class); when(mockTokenSource.getToken()) - .thenReturn(new Token("test-token", "Bearer", LocalDateTime.now().plusHours(1))); + .thenReturn(new Token("test-token", "Bearer", java.time.Instant.now().plusSeconds(3600))); OAuthHeaderFactory mockHeaderFactory = OAuthHeaderFactory.fromTokenSource(mockTokenSource); CredentialsProvider mockProvider = mock(CredentialsProvider.class); when(mockProvider.authType()).thenReturn("test"); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DataPlaneTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DataPlaneTokenSourceTest.java index 5887c4ee1..1fb96a559 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DataPlaneTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/DataPlaneTokenSourceTest.java @@ -8,7 +8,7 @@ import com.databricks.sdk.core.http.Response; import java.io.IOException; import java.net.URL; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,8 +29,7 @@ public class DataPlaneTokenSourceTest { private static Stream provideDataPlaneTokenScenarios() throws Exception { // Mock DatabricksOAuthTokenSource for control plane token - Token cpToken = - new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, LocalDateTime.now().plusSeconds(600)); + Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, Instant.now().plusSeconds(600)); DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class); when(mockCpTokenSource.getToken()).thenReturn(cpToken); @@ -81,9 +80,8 @@ private static Stream provideDataPlaneTokenScenarios() throws Excepti "dp-access-token1", TEST_TOKEN_TYPE, TEST_REFRESH_TOKEN, - LocalDateTime.now().plusSeconds(TEST_EXPIRES_IN)), - null // No exception - ), + Instant.now().plusSeconds(TEST_EXPIRES_IN)), + null), Arguments.of( "Success: endpoint2/auth2 (different cache key)", TEST_ENDPOINT_2, @@ -95,7 +93,7 @@ private static Stream provideDataPlaneTokenScenarios() throws Excepti "dp-access-token2", TEST_TOKEN_TYPE, TEST_REFRESH_TOKEN, - LocalDateTime.now().plusSeconds(TEST_EXPIRES_IN)), + Instant.now().plusSeconds(TEST_EXPIRES_IN)), null), Arguments.of( "Error response from endpoint", @@ -203,7 +201,7 @@ void testDataPlaneTokenSource( @Test void testEndpointTokenSourceCaching() throws Exception { Token cpToken = - new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, LocalDateTime.now().plusSeconds(3600)); + new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, null, Instant.now().plusSeconds(3600)); DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class); when(mockCpTokenSource.getToken()).thenReturn(cpToken); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/EndpointTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/EndpointTokenSourceTest.java index a3af2254f..5fdac9f2d 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/EndpointTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/EndpointTokenSourceTest.java @@ -9,7 +9,7 @@ import com.databricks.sdk.core.http.Response; import java.io.IOException; import java.net.URL; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -48,7 +48,7 @@ private static Stream provideEndpointTokenScenarios() throws Exceptio String malformedJson = "{not valid json}"; // Mock DatabricksOAuthTokenSource for control plane token - Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, LocalDateTime.now().plusMinutes(10)); + Token cpToken = new Token(TEST_CP_TOKEN, TEST_TOKEN_TYPE, Instant.now().plusSeconds(600)); DatabricksOAuthTokenSource mockCpTokenSource = mock(DatabricksOAuthTokenSource.class); when(mockCpTokenSource.getToken()).thenReturn(cpToken); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java index 1714b731c..932690bd7 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProviderTest.java @@ -13,7 +13,7 @@ import com.databricks.sdk.core.http.Response; import java.io.IOException; import java.net.URL; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -210,7 +210,7 @@ void sessionCredentials() throws IOException { "originalAccessToken", "originalTokenType", "originalRefreshToken", - LocalDateTime.MAX)) + Instant.MAX)) .build(); Token token = sessionCredentials.refresh(); @@ -234,7 +234,7 @@ void cacheWithValidTokenTest() throws IOException { .execute(any(Request.class)); // Create an valid token with valid refresh token - LocalDateTime futureTime = LocalDateTime.now().plusHours(1); + Instant futureTime = Instant.now().plusSeconds(3600); Token validToken = new Token("valid_access_token", "Bearer", "valid_refresh_token", futureTime); // Create mock token cache that returns the valid token @@ -314,7 +314,7 @@ void cacheWithInvalidAccessTokenValidRefreshTest() throws IOException { .execute(any(Request.class)); // Create an expired token with valid refresh token - LocalDateTime pastTime = LocalDateTime.now().minusHours(1); + Instant pastTime = Instant.now().minusSeconds(3600); Token expiredToken = new Token("expired_access_token", "Bearer", "valid_refresh_token", pastTime); @@ -392,7 +392,7 @@ void cacheWithInvalidAccessTokenRefreshFailingTest() throws IOException { .execute(any(Request.class)); // Create an expired token with invalid refresh token - LocalDateTime pastTime = LocalDateTime.now().minusHours(1); + Instant pastTime = Instant.now().minusSeconds(3600); Token expiredToken = new Token("expired_access_token", "Bearer", "invalid_refresh_token", pastTime); @@ -406,7 +406,7 @@ void cacheWithInvalidAccessTokenRefreshFailingTest() throws IOException { "browser_access_token", "Bearer", "browser_refresh_token", - LocalDateTime.now().plusHours(1)); + Instant.now().plusSeconds(3600)); SessionCredentials browserAuthCreds = new SessionCredentials.Builder() @@ -460,7 +460,7 @@ void cacheWithInvalidAccessTokenRefreshFailingTest() throws IOException { @Test void cacheWithInvalidTokensTest() throws IOException { // Create completely invalid token (no refresh token) - LocalDateTime pastTime = LocalDateTime.now().minusHours(1); + Instant pastTime = Instant.now().minusSeconds(3600); Token invalidToken = new Token("expired_access_token", "Bearer", null, pastTime); // Create mock token cache that returns the invalid token @@ -473,7 +473,7 @@ void cacheWithInvalidTokensTest() throws IOException { "browser_access_token", "Bearer", "browser_refresh_token", - LocalDateTime.now().plusHours(1)); + Instant.now().plusSeconds(3600)); SessionCredentials browserAuthCreds = new SessionCredentials.Builder() diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/FileTokenCacheTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/FileTokenCacheTest.java index ede6cfd11..303f6de66 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/FileTokenCacheTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/FileTokenCacheTest.java @@ -5,7 +5,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -42,7 +42,7 @@ void testEmptyCache() { @Test void testSaveAndLoadToken() { // Given a token - LocalDateTime expiry = LocalDateTime.now().plusHours(1); + Instant expiry = Instant.now().plusSeconds(3600); Token token = new Token("access-token", "Bearer", "refresh-token", expiry); // When saving and loading the token @@ -60,14 +60,14 @@ void testSaveAndLoadToken() { @Test void testTokenExpiry() { // Create an expired token - LocalDateTime pastTime = LocalDateTime.now().minusHours(1); + Instant pastTime = Instant.now().minusSeconds(3600); Token expiredToken = new Token("access-token", "Bearer", "refresh-token", pastTime); // Verify it's marked as expired assertTrue(expiredToken.isExpired(), "Token should be expired"); // Create a valid token - LocalDateTime futureTime = LocalDateTime.now().plusMinutes(30); + Instant futureTime = Instant.now().plusSeconds(1800); Token validToken = new Token("access-token", "Bearer", "refresh-token", futureTime); // Verify it's not marked as expired @@ -86,8 +86,8 @@ void testNullPathRejection() { @Test void testOverwriteToken() { // Given two tokens saved in sequence - Token token1 = new Token("token1", "Bearer", "refresh1", LocalDateTime.now().plusHours(1)); - Token token2 = new Token("token2", "Bearer", "refresh2", LocalDateTime.now().plusHours(2)); + Token token1 = new Token("token1", "Bearer", "refresh1", Instant.now().plusSeconds(3600)); + Token token2 = new Token("token2", "Bearer", "refresh2", Instant.now().plusSeconds(7200)); tokenCache.save(token1); tokenCache.save(token2); @@ -110,7 +110,7 @@ void testWithCustomPath(@TempDir Path tempDir) { // And a token Token testToken = new Token( - "test-access-token", "Bearer", "test-refresh-token", LocalDateTime.now().plusHours(1)); + "test-access-token", "Bearer", "test-refresh-token", Instant.now().plusSeconds(3600)); // When saving and loading cache.save(testToken); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java index d0530b2c1..f0b83153c 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/OAuthHeaderFactoryTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -25,7 +25,7 @@ public class OAuthHeaderFactoryTest { @Mock private TokenSource tokenSource; private static Stream provideTokenSourceTestCases() { - LocalDateTime expiry = LocalDateTime.now().plusHours(1); + Instant expiry = Instant.now().plusSeconds(3600); Token token = new Token(TOKEN_VALUE, TOKEN_TYPE, expiry); return Stream.of( @@ -57,7 +57,7 @@ public void testFromTokenSourceFactoryMethod( } private static Stream provideSuppliersTestCases() { - LocalDateTime expiry = LocalDateTime.now().plusHours(1); + Instant expiry = Instant.now().plusSeconds(3600); Token token = new Token(TOKEN_VALUE, TOKEN_TYPE, expiry); Map standardHeaders = new HashMap<>(); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenSourceCredentialsProviderTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenSourceCredentialsProviderTest.java index 14eb3fa40..8d2d68fd4 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenSourceCredentialsProviderTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenSourceCredentialsProviderTest.java @@ -6,7 +6,7 @@ import com.databricks.sdk.core.DatabricksConfig; import com.databricks.sdk.core.DatabricksException; import com.databricks.sdk.core.HeaderFactory; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -19,7 +19,7 @@ class TokenSourceCredentialsProviderTest { private static final String TEST_AUTH_TYPE = "test-auth-type"; private static final String TEST_ACCESS_TOKEN_VALUE = "test-access-token"; private static final Token TEST_TOKEN = - new Token(TEST_ACCESS_TOKEN_VALUE, "Bearer", LocalDateTime.now().plusHours(1)); + new Token(TEST_ACCESS_TOKEN_VALUE, "Bearer", Instant.now().plusSeconds(3600)); /** Tests token retrieval scenarios */ @ParameterizedTest(name = "{0}") diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java index 2d87a32c2..16b726222 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/oauth/TokenTest.java @@ -4,7 +4,6 @@ import com.databricks.sdk.core.utils.FakeClockSupplier; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import org.junit.jupiter.api.Test; @@ -13,20 +12,20 @@ class TokenTest { private static final String accessToken = "testAccessToken"; private static final String refreshToken = "testRefreshToken"; private static final String tokenType = "testTokenType"; - private final LocalDateTime currentLocalDateTime; + private final Instant currentInstant; private final FakeClockSupplier fakeClockSupplier; TokenTest() { Instant instant = Instant.parse("2023-10-18T12:00:00.00Z"); ZoneId zoneId = ZoneId.of("UTC"); fakeClockSupplier = new FakeClockSupplier(instant, zoneId); - currentLocalDateTime = LocalDateTime.now(fakeClockSupplier.getClock()); + currentInstant = Instant.now(fakeClockSupplier.getClock()); } @Test void createNonRefreshableToken() { Token token = - new Token(accessToken, tokenType, currentLocalDateTime.plusMinutes(5), fakeClockSupplier); + new Token(accessToken, tokenType, currentInstant.plusSeconds(300), fakeClockSupplier); assertEquals(accessToken, token.getAccessToken()); assertEquals(tokenType, token.getTokenType()); assertNull(token.getRefreshToken()); @@ -40,7 +39,7 @@ void createRefreshableToken() { accessToken, tokenType, refreshToken, - currentLocalDateTime.plusMinutes(5), + currentInstant.plusSeconds(300), fakeClockSupplier); assertEquals(accessToken, token.getAccessToken()); assertEquals(tokenType, token.getTokenType()); @@ -51,7 +50,7 @@ void createRefreshableToken() { @Test void tokenExpiryMoreThan40Seconds() { Token token = - new Token(accessToken, tokenType, currentLocalDateTime.plusSeconds(50), fakeClockSupplier); + new Token(accessToken, tokenType, currentInstant.plusSeconds(50), fakeClockSupplier); assertFalse(token.isExpired()); assertTrue(token.isValid()); } @@ -59,7 +58,7 @@ void tokenExpiryMoreThan40Seconds() { @Test void tokenExpiryLessThan40Seconds() { Token token = - new Token(accessToken, tokenType, currentLocalDateTime.plusSeconds(30), fakeClockSupplier); + new Token(accessToken, tokenType, currentInstant.plusSeconds(30), fakeClockSupplier); assertTrue(token.isExpired()); assertFalse(token.isValid()); } @@ -67,7 +66,7 @@ void tokenExpiryLessThan40Seconds() { @Test void expiredToken() { Token token = - new Token(accessToken, tokenType, currentLocalDateTime.minusSeconds(10), fakeClockSupplier); + new Token(accessToken, tokenType, currentInstant.minusSeconds(10), fakeClockSupplier); assertTrue(token.isExpired()); assertFalse(token.isValid()); } From 6f81b4cff985b9ad06665189c39eac630d1a5806 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 4 Jun 2025 08:51:23 +0000 Subject: [PATCH 02/26] Update parseExpiry in CilTokenSource --- .../main/java/com/databricks/sdk/core/CliTokenSource.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 3b3845e51..9eec8930f 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -41,8 +41,8 @@ public CliTokenSource( /** * Parses an expiry time string and returns the corresponding {@link Instant}. * - *

The expiry time string is verified to always be in UTC. Any time zone or offset information - * present in the input is ignored, and the value is parsed as a UTC time. + *

The expiry time string is always in UTC. Any time zone or offset information present in the + * input is ignored. * *

The method attempts to parse the input using several common date-time formats, including * ISO-8601 and patterns with varying sub-second precision. @@ -53,6 +53,7 @@ public CliTokenSource( */ static Instant parseExpiry(String expiry) { DateTimeParseException lastException = null; + // Try to parse the expiry as an ISO-8601 string in UTC first try { return Instant.parse(expiry); } catch (DateTimeParseException e) { From daac1b2f8ec1935c9ee4be4553fc615ac44915ff Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 4 Jun 2025 09:02:34 +0000 Subject: [PATCH 03/26] Update javadoc --- .../src/main/java/com/databricks/sdk/core/CliTokenSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 9eec8930f..d07bbf787 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -49,7 +49,7 @@ public CliTokenSource( * * @param expiry the expiry time string to parse, which must represent a UTC time * @return the parsed {@link Instant} representing the expiry time in UTC - * @throws DateTimeParseException if the input string cannot be parsed as a valid date-time + * @throws DateTimeParseException if the input string cannot be parsed */ static Instant parseExpiry(String expiry) { DateTimeParseException lastException = null; From f3d4b8a669d12f6bb3ffb56054e71c7f6cdb7eff Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 4 Jun 2025 09:03:51 +0000 Subject: [PATCH 04/26] Update javadoc --- .../src/main/java/com/databricks/sdk/core/CliTokenSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index d07bbf787..2e176c210 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -48,7 +48,7 @@ public CliTokenSource( * ISO-8601 and patterns with varying sub-second precision. * * @param expiry the expiry time string to parse, which must represent a UTC time - * @return the parsed {@link Instant} representing the expiry time in UTC + * @return the parsed {@link Instant} * @throws DateTimeParseException if the input string cannot be parsed */ static Instant parseExpiry(String expiry) { From 12123a957402ab4bc492de71cb240b7842211a84 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 5 Jun 2025 09:15:03 +0000 Subject: [PATCH 05/26] Retrigger tests From fdc50effa6c3d0c22bffe247e55ee3bb2acb18c8 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 6 Jun 2025 11:00:55 +0000 Subject: [PATCH 06/26] Removed redundant date formattters --- .../databricks/sdk/core/CliTokenSource.java | 42 ++-------- .../sdk/core/CliTokenSourceTest.java | 79 ++++++++----------- .../src/test/resources/testdata/az | 8 +- 3 files changed, 45 insertions(+), 84 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 2e176c210..0e68dc7fc 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -9,11 +9,8 @@ import java.io.IOException; import java.io.InputStream; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; +import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; -import java.util.Arrays; import java.util.List; import org.apache.commons.io.IOUtils; @@ -39,44 +36,15 @@ public CliTokenSource( } /** - * Parses an expiry time string and returns the corresponding {@link Instant}. + * Parses an expiry string in RFC 3339/ISO 8601 format (with or without offset) and returns the + * corresponding {@link Instant}. Any specified time zone or offset is converted to UTC. * - *

The expiry time string is always in UTC. Any time zone or offset information present in the - * input is ignored. - * - *

The method attempts to parse the input using several common date-time formats, including - * ISO-8601 and patterns with varying sub-second precision. - * - * @param expiry the expiry time string to parse, which must represent a UTC time + * @param expiry expiry time string in RFC 3339/ISO 8601 format * @return the parsed {@link Instant} * @throws DateTimeParseException if the input string cannot be parsed */ static Instant parseExpiry(String expiry) { - DateTimeParseException lastException = null; - // Try to parse the expiry as an ISO-8601 string in UTC first - try { - return Instant.parse(expiry); - } catch (DateTimeParseException e) { - lastException = e; - } - - String multiplePrecisionPattern = - "[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]"; - List datePatterns = - Arrays.asList( - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss." + multiplePrecisionPattern, - "yyyy-MM-dd'T'HH:mm:ss." + multiplePrecisionPattern + "XXX"); - for (String pattern : datePatterns) { - try { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); - LocalDateTime dateTime = LocalDateTime.parse(expiry, formatter); - return dateTime.atZone(ZoneOffset.UTC).toInstant(); - } catch (DateTimeParseException e) { - lastException = e; - } - } - throw lastException; + return OffsetDateTime.parse(expiry).toInstant(); } private String getProcessStream(InputStream stream) throws IOException { diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index abe609b01..cc177881c 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -1,58 +1,49 @@ package com.databricks.sdk.core; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.Instant; import java.time.format.DateTimeParseException; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class CliTokenSourceTest { - @Test - public void testParseExpiryWithoutTruncate() { - Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z"); - assertEquals(Instant.parse("2023-07-17T09:02:22.330612218Z"), parsedInstant); + private static Stream expiryProvider() { + return Stream.of( + Arguments.of( + "2023-07-17T09:02:22.330612218Z", "2023-07-17T09:02:22.330612218Z", "9-digit nanos"), + Arguments.of( + "2023-07-17T09:02:22.33061221Z", "2023-07-17T09:02:22.330612210Z", "8-digit nanos"), + Arguments.of( + "2023-07-17T09:02:22.330612Z", "2023-07-17T09:02:22.330612000Z", "6-digit nanos"), + Arguments.of( + "2023-07-17T10:02:22.330612218+01:00", + "2023-07-17T09:02:22.330612218Z", + "+01:00 offset, 9-digit nanos"), + Arguments.of( + "2023-07-17T04:02:22.330612218-05:00", + "2023-07-17T09:02:22.330612218Z", + "-05:00 offset, 9-digit nanos"), + Arguments.of( + "2023-07-17T10:02:22.330612+01:00", + "2023-07-17T09:02:22.330612000Z", + "+01:00 offset, 6-digit nanos"), + Arguments.of("2023-07-17T09:02:22.33061221987Z", null, "Invalid: >9 nanos"), + Arguments.of("17-07-2023 09:02:22", null, "Invalid: non-ISO8601 format")); } - @Test - public void testParseExpiryWithTruncate() { - Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221Z"); - assertEquals(Instant.parse("2023-07-17T09:02:22.330612210Z"), parsedInstant); - } - - @Test - public void testParseExpiryWithTruncateAndLessNanoSecondDigits() { - Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612Z"); - assertEquals(Instant.parse("2023-07-17T09:02:22.330612000Z"), parsedInstant); - } - - @Test - public void testParseExpiryWithMoreThanNineNanoSecondDigits() { - try { - CliTokenSource.parseExpiry("2023-07-17T09:02:22.33061221987Z"); - } catch (DateTimeParseException e) { - assert (e.getMessage().contains("could not be parsed")); - } - } - - @Test - public void testParseExpiryWithSpaceFormat() { - Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22"); - assertEquals(Instant.parse("2023-07-17T09:02:22Z"), parsedInstant); - } - - @Test - public void testParseExpiryWithSpaceFormatAndMillis() { - Instant parsedInstant = CliTokenSource.parseExpiry("2023-07-17 09:02:22.123"); - assertEquals(Instant.parse("2023-07-17T09:02:22.123Z"), parsedInstant); - } - - @Test - public void testParseExpiryWithInvalidFormat() { - try { - CliTokenSource.parseExpiry("17-07-2023 09:02:22"); - } catch (DateTimeParseException e) { - assert (e.getMessage().contains("could not be parsed")); + @ParameterizedTest(name = "{2}") + @MethodSource("expiryProvider") + public void testParseExpiry(String input, String expectedInstant, String description) { + if (expectedInstant == null) { + assertThrows(DateTimeParseException.class, () -> CliTokenSource.parseExpiry(input)); + } else { + Instant parsedInstant = CliTokenSource.parseExpiry(input); + assertEquals(Instant.parse(expectedInstant), parsedInstant); } } } diff --git a/databricks-sdk-java/src/test/resources/testdata/az b/databricks-sdk-java/src/test/resources/testdata/az index 29b824ed7..b24bddea9 100755 --- a/databricks-sdk-java/src/test/resources/testdata/az +++ b/databricks-sdk-java/src/test/resources/testdata/az @@ -22,14 +22,16 @@ for arg in "$@"; do fi done -# Macos -EXP="$(/bin/date -v+${EXPIRE:=10S} +'%F %T' 2>/dev/null)" +# MacOS +EXP="$(/bin/date -v+${EXPIRE:=10S} +'%FT%T%z' 2>/dev/null)" if [ -z "${EXP}" ]; then # Linux EXPIRE=$(/bin/echo $EXPIRE | /bin/sed 's/S/seconds/') EXPIRE=$(/bin/echo $EXPIRE | /bin/sed 's/M/minutes/') - EXP=$(/bin/date --date=+${EXPIRE:=10seconds} +'%F %T') + EXP=$(/bin/date --date=+${EXPIRE:=10seconds} +'%FT%T%z') fi +# Insert colon in timezone offset for ISO 8601 compliance +EXP="${EXP:0:19}${EXP:19:3}:${EXP:22:2}" if [ -z "${TF_AAD_TOKEN}" ]; then TF_AAD_TOKEN="..." From 70934b27ca4769d241bd3d02ff02e1d1e58c13d5 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 6 Jun 2025 14:08:29 +0000 Subject: [PATCH 07/26] Change clock supplier to use UTC time --- .../java/com/databricks/sdk/core/utils/SystemClockSupplier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SystemClockSupplier.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SystemClockSupplier.java index edac0cd94..c79e6884d 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SystemClockSupplier.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SystemClockSupplier.java @@ -5,6 +5,6 @@ public class SystemClockSupplier implements ClockSupplier { @Override public Clock getClock() { - return Clock.systemDefaultZone(); + return Clock.systemUTC(); } } From 1bd052f1a4630b6c343fa7b01e6a474c45a2f8e9 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Sat, 7 Jun 2025 18:03:50 +0000 Subject: [PATCH 08/26] Add support for space separated expiry strings --- .../databricks/sdk/core/CliTokenSource.java | 26 +++++++++++- .../sdk/core/CliTokenSourceTest.java | 40 ++++++++++++++----- .../src/test/resources/testdata/az | 10 ++--- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 0e68dc7fc..9313a4b3f 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -9,8 +9,12 @@ import java.io.IOException; import java.io.InputStream; import java.time.Instant; +import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Arrays; import java.util.List; import org.apache.commons.io.IOUtils; @@ -44,7 +48,27 @@ public CliTokenSource( * @throws DateTimeParseException if the input string cannot be parsed */ static Instant parseExpiry(String expiry) { - return OffsetDateTime.parse(expiry).toInstant(); + DateTimeParseException lastException = null; + try { + return OffsetDateTime.parse(expiry).toInstant(); + } catch (DateTimeParseException e) { + lastException = e; + } + + String multiplePrecisionPattern = + "[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]"; + List datePatterns = + Arrays.asList("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss." + multiplePrecisionPattern); + for (String pattern : datePatterns) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + LocalDateTime dateTime = LocalDateTime.parse(expiry, formatter); + return dateTime.atZone(ZoneId.systemDefault()).toInstant(); + } catch (DateTimeParseException e) { + lastException = e; + } + } + throw lastException; } private String getProcessStream(InputStream stream) throws IOException { diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index cc177881c..d6c8fc740 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.DateTimeParseException; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -15,35 +17,55 @@ public class CliTokenSourceTest { private static Stream expiryProvider() { return Stream.of( Arguments.of( - "2023-07-17T09:02:22.330612218Z", "2023-07-17T09:02:22.330612218Z", "9-digit nanos"), + "2023-07-17T09:02:22.330612218Z", + Instant.parse("2023-07-17T09:02:22.330612218Z"), + "9-digit nanos"), Arguments.of( - "2023-07-17T09:02:22.33061221Z", "2023-07-17T09:02:22.330612210Z", "8-digit nanos"), + "2023-07-17T09:02:22.33061221Z", + Instant.parse("2023-07-17T09:02:22.330612210Z"), + "8-digit nanos"), Arguments.of( - "2023-07-17T09:02:22.330612Z", "2023-07-17T09:02:22.330612000Z", "6-digit nanos"), + "2023-07-17T09:02:22.330612Z", + Instant.parse("2023-07-17T09:02:22.330612000Z"), + "6-digit nanos"), Arguments.of( "2023-07-17T10:02:22.330612218+01:00", - "2023-07-17T09:02:22.330612218Z", + Instant.parse("2023-07-17T09:02:22.330612218Z"), "+01:00 offset, 9-digit nanos"), Arguments.of( "2023-07-17T04:02:22.330612218-05:00", - "2023-07-17T09:02:22.330612218Z", + Instant.parse("2023-07-17T09:02:22.330612218Z"), "-05:00 offset, 9-digit nanos"), Arguments.of( "2023-07-17T10:02:22.330612+01:00", - "2023-07-17T09:02:22.330612000Z", + Instant.parse("2023-07-17T09:02:22.330612000Z"), "+01:00 offset, 6-digit nanos"), Arguments.of("2023-07-17T09:02:22.33061221987Z", null, "Invalid: >9 nanos"), - Arguments.of("17-07-2023 09:02:22", null, "Invalid: non-ISO8601 format")); + Arguments.of("17-07-2023 09:02:22", null, "Invalid date format"), + Arguments.of( + "2023-07-17 09:02:22.330612218", + LocalDateTime.parse("2023-07-17T09:02:22.330612218") + .atZone(ZoneId.systemDefault()) + .toInstant(), + "Space separator, 9-digit nanos"), + Arguments.of( + "2023-07-17 09:02:22.330612", + LocalDateTime.parse("2023-07-17T09:02:22.330612") + .atZone(ZoneId.systemDefault()) + .toInstant(), + "Space separator, 6-digit nanos"), + Arguments.of( + "2023-07-17 09:02:22.33061221987", null, "Space separator, Invalid: >9 nanos")); } @ParameterizedTest(name = "{2}") @MethodSource("expiryProvider") - public void testParseExpiry(String input, String expectedInstant, String description) { + public void testParseExpiry(String input, Instant expectedInstant, String description) { if (expectedInstant == null) { assertThrows(DateTimeParseException.class, () -> CliTokenSource.parseExpiry(input)); } else { Instant parsedInstant = CliTokenSource.parseExpiry(input); - assertEquals(Instant.parse(expectedInstant), parsedInstant); + assertEquals(expectedInstant, parsedInstant); } } } diff --git a/databricks-sdk-java/src/test/resources/testdata/az b/databricks-sdk-java/src/test/resources/testdata/az index b24bddea9..00997a4cf 100755 --- a/databricks-sdk-java/src/test/resources/testdata/az +++ b/databricks-sdk-java/src/test/resources/testdata/az @@ -22,16 +22,14 @@ for arg in "$@"; do fi done -# MacOS -EXP="$(/bin/date -v+${EXPIRE:=10S} +'%FT%T%z' 2>/dev/null)" +# Macos +EXP="$(/bin/date -v+${EXPIRE:=10S} +'%F %T' 2>/dev/null)" if [ -z "${EXP}" ]; then # Linux EXPIRE=$(/bin/echo $EXPIRE | /bin/sed 's/S/seconds/') EXPIRE=$(/bin/echo $EXPIRE | /bin/sed 's/M/minutes/') - EXP=$(/bin/date --date=+${EXPIRE:=10seconds} +'%FT%T%z') + EXP=$(/bin/date --date=+${EXPIRE:=10seconds} +'%F %T') fi -# Insert colon in timezone offset for ISO 8601 compliance -EXP="${EXP:0:19}${EXP:19:3}:${EXP:22:2}" if [ -z "${TF_AAD_TOKEN}" ]; then TF_AAD_TOKEN="..." @@ -43,4 +41,4 @@ fi \"subscription\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"tenant\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"tokenType\": \"Bearer\" -}" +}" \ No newline at end of file From 408f3b465307952d7bad140f92d8a71ab1b78e2b Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Sat, 7 Jun 2025 18:09:32 +0000 Subject: [PATCH 09/26] revert test data --- databricks-sdk-java/src/test/resources/testdata/az | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databricks-sdk-java/src/test/resources/testdata/az b/databricks-sdk-java/src/test/resources/testdata/az index 00997a4cf..29b824ed7 100755 --- a/databricks-sdk-java/src/test/resources/testdata/az +++ b/databricks-sdk-java/src/test/resources/testdata/az @@ -41,4 +41,4 @@ fi \"subscription\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"tenant\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"tokenType\": \"Bearer\" -}" \ No newline at end of file +}" From 7fccff9481b0b7c3c10135e128a33471c8fffa78 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 11 Jun 2025 09:50:31 +0000 Subject: [PATCH 10/26] Update exception handling --- .../java/com/databricks/sdk/core/CliTokenSource.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 9313a4b3f..5b380a84b 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -48,11 +48,11 @@ public CliTokenSource( * @throws DateTimeParseException if the input string cannot be parsed */ static Instant parseExpiry(String expiry) { - DateTimeParseException lastException = null; + DateTimeParseException parseException; try { return OffsetDateTime.parse(expiry).toInstant(); } catch (DateTimeParseException e) { - lastException = e; + parseException = e; } String multiplePrecisionPattern = @@ -65,10 +65,11 @@ static Instant parseExpiry(String expiry) { LocalDateTime dateTime = LocalDateTime.parse(expiry, formatter); return dateTime.atZone(ZoneId.systemDefault()).toInstant(); } catch (DateTimeParseException e) { - lastException = e; + parseException.addSuppressed(e); } } - throw lastException; + + throw parseException; } private String getProcessStream(InputStream stream) throws IOException { From 64313f862d91811e305c145c54d20a40bdd667bc Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 11 Jun 2025 13:19:36 +0000 Subject: [PATCH 11/26] Update Javadoc --- .../com/databricks/sdk/core/CliTokenSource.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 5b380a84b..ff0740caa 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -40,12 +40,19 @@ public CliTokenSource( } /** - * Parses an expiry string in RFC 3339/ISO 8601 format (with or without offset) and returns the - * corresponding {@link Instant}. Any specified time zone or offset is converted to UTC. + * Parses an expiry time string and returns the corresponding {@link Instant}. The method attempts + * to parse the input in the following order: 1. RFC 3339/ISO 8601 format with offset (e.g. + * "2024-03-20T10:30:00Z") 2. Local date-time format "yyyy-MM-dd HH:mm:ss" (e.g. "2024-03-20 + * 10:30:00") 3. Local date-time format with optional fractional seconds of varying precision + * (e.g. "2024-03-20 10:30:00.123") * - * @param expiry expiry time string in RFC 3339/ISO 8601 format + *

Any specified time zone or offset is converted to UTC. For local date-time formats, the + * system's default time zone is used. + * + * @param expiry expiry time string in one of the supported formats * @return the parsed {@link Instant} - * @throws DateTimeParseException if the input string cannot be parsed + * @throws DateTimeParseException if the input string cannot be parsed in any of the supported + * formats */ static Instant parseExpiry(String expiry) { DateTimeParseException parseException; From 447eae227c0b1369346d658c2039b654e59dfd17 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Wed, 11 Jun 2025 23:38:47 +0000 Subject: [PATCH 12/26] Added more tests to CilTokenSourceTest --- .../sdk/core/CliTokenSourceTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 20e2f6095..f1d230532 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -1,12 +1,104 @@ package com.databricks.sdk.core; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import com.databricks.sdk.core.oauth.Token; +import com.databricks.sdk.core.utils.Environment; +import com.databricks.sdk.core.utils.OSUtils; +import com.databricks.sdk.core.utils.OSUtilities; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; public class CliTokenSourceTest { + private static final String[] DATE_FORMATS = { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + }; + + String getExpiryStr(String dateFormat, Duration offset) { + ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset); + return futureExpiry.format(DateTimeFormatter.ofPattern(dateFormat)); + } + + private static Stream provideTestCases() { + return Stream.of( + Arguments.of("Valid: 30min remaining", 30, false), + Arguments.of("Valid: 1hr remaining", 60, false), + Arguments.of("Valid: 2hrs remaining", 120, false), + Arguments.of("Expired: 30min ago", -30, true), + Arguments.of("Expired: 1hr ago", -60, true), + Arguments.of("Expired: 2hrs ago", -120, true) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("provideTestCases") + public void testRefreshWithExpiry(String testName, int offsetMinutes, boolean shouldBeExpired) throws IOException, InterruptedException { + for (String dateFormat : DATE_FORMATS) { + // Mock environment + Environment env = mock(Environment.class); + Map envMap = new HashMap<>(); + when(env.getEnv()).thenReturn(envMap); + + // Create test command + List cmd = Arrays.asList("test", "command"); + + // Mock OSUtilities + OSUtilities osUtils = mock(OSUtilities.class); + when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); + + try (MockedStatic mockedOSUtils = mockStatic(OSUtils.class)) { + mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils); + + // Create token source + CliTokenSource tokenSource = new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); + + String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(offsetMinutes)); + + // Mock process + Process process = mock(Process.class); + when(process.getInputStream()).thenReturn(new ByteArrayInputStream( + String.format("{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", expiryStr).getBytes())); + when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); + when(process.waitFor()).thenReturn(0); + + // Mock ProcessBuilder constructor + try (MockedConstruction mocked = mockConstruction(ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(process); + })) { + // Test refresh + Token token = tokenSource.refresh(); + assertEquals("Bearer", token.getTokenType()); + assertEquals("test-token", token.getAccessToken()); + assertEquals(shouldBeExpired, token.isExpired()); + } + } + } + } @Test public void testParseExpiryWithoutTruncate() { From 66335a7098af438045f088d1fbd48613502f6abf Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 12:02:48 +0000 Subject: [PATCH 13/26] Add test to verify perserved behaviour --- .../sdk/core/CliTokenSourceTest.java | 104 ++++++++++++------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index f1d230532..a781159b4 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -9,8 +9,8 @@ import com.databricks.sdk.core.oauth.Token; import com.databricks.sdk.core.utils.Environment; -import com.databricks.sdk.core.utils.OSUtils; import com.databricks.sdk.core.utils.OSUtilities; +import com.databricks.sdk.core.utils.OSUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.time.Duration; @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -32,10 +33,10 @@ public class CliTokenSourceTest { private static final String[] DATE_FORMATS = { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" }; String getExpiryStr(String dateFormat, Duration offset) { @@ -43,20 +44,9 @@ String getExpiryStr(String dateFormat, Duration offset) { return futureExpiry.format(DateTimeFormatter.ofPattern(dateFormat)); } - private static Stream provideTestCases() { - return Stream.of( - Arguments.of("Valid: 30min remaining", 30, false), - Arguments.of("Valid: 1hr remaining", 60, false), - Arguments.of("Valid: 2hrs remaining", 120, false), - Arguments.of("Expired: 30min ago", -30, true), - Arguments.of("Expired: 1hr ago", -60, true), - Arguments.of("Expired: 2hrs ago", -120, true) - ); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("provideTestCases") - public void testRefreshWithExpiry(String testName, int offsetMinutes, boolean shouldBeExpired) throws IOException, InterruptedException { + public void testRefreshWithExpiry( + String testName, int minutesUntilExpiry, boolean shouldBeExpired) + throws IOException, InterruptedException { for (String dateFormat : DATE_FORMATS) { // Mock environment Environment env = mock(Environment.class); @@ -65,31 +55,38 @@ public void testRefreshWithExpiry(String testName, int offsetMinutes, boolean sh // Create test command List cmd = Arrays.asList("test", "command"); - + // Mock OSUtilities OSUtilities osUtils = mock(OSUtilities.class); when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); - + try (MockedStatic mockedOSUtils = mockStatic(OSUtils.class)) { mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils); - - // Create token source - CliTokenSource tokenSource = new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); - String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(offsetMinutes)); + CliTokenSource tokenSource = + new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); + + String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); // Mock process Process process = mock(Process.class); - when(process.getInputStream()).thenReturn(new ByteArrayInputStream( - String.format("{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", expiryStr).getBytes())); + when(process.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + String.format( + "{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", + expiryStr) + .getBytes())); when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); when(process.waitFor()).thenReturn(0); // Mock ProcessBuilder constructor - try (MockedConstruction mocked = mockConstruction(ProcessBuilder.class, - (mock, context) -> { - when(mock.start()).thenReturn(process); - })) { + try (MockedConstruction mocked = + mockConstruction( + ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(process); + })) { // Test refresh Token token = tokenSource.refresh(); assertEquals("Bearer", token.getTokenType()); @@ -100,6 +97,51 @@ public void testRefreshWithExpiry(String testName, int offsetMinutes, boolean sh } } + private static Stream provideTimezoneTestCases() { + // Timezones to test + List timezones = Arrays.asList("UTC", "GMT+1", "GMT+8", "GMT-1", "GMT-8"); + + // Time to expiry of tokens (minutes, shouldBeExpired) + List minutesUntilExpiry = + Arrays.asList( + Arguments.of(5, false), // 5 minutes remaining + Arguments.of(30, false), // 30 minutes remaining + Arguments.of(60, false), // 1 hour remaining + Arguments.of(120, false), // 2 hours remaining + Arguments.of(-5, true), // 5 minutes ago + Arguments.of(-30, true), // 30 minutes ago + Arguments.of(-60, true), // 1 hour ago + Arguments.of(-120, true) // 2 hours ago + ); + + // Create cross product of timezones and minutesUntilExpiry cases + return timezones.stream() + .flatMap( + timezone -> + minutesUntilExpiry.stream() + .map( + minutesUntilExpiryCase -> { + Object[] args = minutesUntilExpiryCase.get(); + return Arguments.of(timezone, args[0], args[1]); + })); + } + + @ParameterizedTest(name = "Test in {0} with {1} minutes offset") + @MethodSource("provideTimezoneTestCases") + public void testRefreshWithDifferentTimezone( + String timezone, int minutesUntilExpiry, boolean shouldBeExpired) + throws IOException, InterruptedException { + // Save original timezone + TimeZone originalTimeZone = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone(timezone)); + testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired); + } finally { + // Restore original timezone + TimeZone.setDefault(originalTimeZone); + } + } + @Test public void testParseExpiryWithoutTruncate() { LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z"); From 3a824ebaf85a8f8483ff034cc78ff579f6e2339b Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 12:48:28 +0000 Subject: [PATCH 14/26] Generate all timezones --- .../com/databricks/sdk/core/CliTokenSourceTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index a781159b4..33c2194e8 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; import java.util.TimeZone; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -68,7 +70,7 @@ public void testRefreshWithExpiry( String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); - // Mock process + // Mock process to return the specified expiry string Process process = mock(Process.class); when(process.getInputStream()) .thenReturn( @@ -98,8 +100,11 @@ public void testRefreshWithExpiry( } private static Stream provideTimezoneTestCases() { - // Timezones to test - List timezones = Arrays.asList("UTC", "GMT+1", "GMT+8", "GMT-1", "GMT-8"); + // Generate timezones from GMT-12 to GMT+12 + List timezones = + IntStream.rangeClosed(-12, 12) + .mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset)) + .collect(Collectors.toList()); // Time to expiry of tokens (minutes, shouldBeExpired) List minutesUntilExpiry = From 4d31c1ea5ce38a6730b3c40918ae9c1412c28346 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 13:49:57 +0000 Subject: [PATCH 15/26] Merge branch 'emmyzhou-db/test_time' into emmyzhou-db/localdatetime-to-instant --- .../sdk/core/CliTokenSourceTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index d6c8fc740..082682482 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -2,18 +2,155 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import com.databricks.sdk.core.oauth.Token; +import com.databricks.sdk.core.utils.Environment; +import com.databricks.sdk.core.utils.OSUtilities; +import com.databricks.sdk.core.utils.OSUtils; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; public class CliTokenSourceTest { + private static final String[] DATE_FORMATS = { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + TimeZone.getDefault().getID().equals("UTC") + ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + }; + + String getExpiryStr(String dateFormat, Duration offset) { + ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset); + return futureExpiry.format(DateTimeFormatter.ofPattern(dateFormat)); + } + + public void testRefreshWithExpiry( + String testName, int minutesUntilExpiry, boolean shouldBeExpired) + throws IOException, InterruptedException { + for (String dateFormat : DATE_FORMATS) { + // Mock environment + Environment env = mock(Environment.class); + Map envMap = new HashMap<>(); + when(env.getEnv()).thenReturn(envMap); + + // Create test command + List cmd = Arrays.asList("test", "command"); + + // Mock OSUtilities + OSUtilities osUtils = mock(OSUtilities.class); + when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); + + try (MockedStatic mockedOSUtils = mockStatic(OSUtils.class)) { + mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils); + + CliTokenSource tokenSource = + new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); + + String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); + + // Mock process to return the specified expiry string + Process process = mock(Process.class); + when(process.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + String.format( + "{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", + expiryStr) + .getBytes())); + when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); + when(process.waitFor()).thenReturn(0); + + // Mock ProcessBuilder constructor + try (MockedConstruction mocked = + mockConstruction( + ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(process); + })) { + // Test refresh + Token token = tokenSource.refresh(); + assertEquals("Bearer", token.getTokenType()); + assertEquals("test-token", token.getAccessToken()); + assertEquals(shouldBeExpired, token.isExpired()); + } + } + } + } + + private static Stream provideTimezoneTestCases() { + // Generate timezones from GMT-12 to GMT+12 + List timezones = + IntStream.rangeClosed(-12, 12) + .mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset)) + .collect(Collectors.toList()); + + // Time to expiry of tokens (minutes, shouldBeExpired) + List minutesUntilExpiry = + Arrays.asList( + Arguments.of(5, false), // 5 minutes remaining + Arguments.of(30, false), // 30 minutes remaining + Arguments.of(60, false), // 1 hour remaining + Arguments.of(120, false), // 2 hours remaining + Arguments.of(-5, true), // 5 minutes ago + Arguments.of(-30, true), // 30 minutes ago + Arguments.of(-60, true), // 1 hour ago + Arguments.of(-120, true) // 2 hours ago + ); + + // Create cross product of timezones and minutesUntilExpiry cases + return timezones.stream() + .flatMap( + timezone -> + minutesUntilExpiry.stream() + .map( + minutesUntilExpiryCase -> { + Object[] args = minutesUntilExpiryCase.get(); + return Arguments.of(timezone, args[0], args[1]); + })); + } + + @ParameterizedTest(name = "Test in {0} with {1} minutes offset") + @MethodSource("provideTimezoneTestCases") + public void testRefreshWithDifferentTimezone( + String timezone, int minutesUntilExpiry, boolean shouldBeExpired) + throws IOException, InterruptedException { + // Save original timezone + TimeZone originalTimeZone = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone(timezone)); + testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired); + } finally { + // Restore original timezone + TimeZone.setDefault(originalTimeZone); + } + } + private static Stream expiryProvider() { return Stream.of( Arguments.of( From 95a3c6d5cd1c7ba5493a57a6acc3ce373b52962e Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 13:52:24 +0000 Subject: [PATCH 16/26] Update test --- .../java/com/databricks/sdk/core/CliTokenSourceTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 33c2194e8..54e762c91 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -37,8 +37,9 @@ public class CliTokenSourceTest { private static final String[] DATE_FORMATS = { "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.SSS", - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + TimeZone.getDefault().getID().equals("UTC") + ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" }; String getExpiryStr(String dateFormat, Duration offset) { From a5c65c5813062be22af809729fec848c5ed62e12 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 14:24:04 +0000 Subject: [PATCH 17/26] update tests --- .../test/java/com/databricks/sdk/core/CliTokenSourceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 082682482..451eafdfe 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -36,7 +36,6 @@ import org.mockito.MockedStatic; public class CliTokenSourceTest { - private static final String[] DATE_FORMATS = { "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.SSS", From a165b72ec9dd7dd009f96cac8ceb08e9f99bdc77 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 14:35:43 +0000 Subject: [PATCH 18/26] Date formats are generated at run-time --- .../sdk/core/CliTokenSourceTest.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 451eafdfe..178390c9c 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -36,13 +36,15 @@ import org.mockito.MockedStatic; public class CliTokenSourceTest { - private static final String[] DATE_FORMATS = { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - TimeZone.getDefault().getID().equals("UTC") - ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - }; + private String[] getDateFormats() { + return new String[] { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + TimeZone.getDefault().getID().equals("UTC") + ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + }; + } String getExpiryStr(String dateFormat, Duration offset) { ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset); @@ -52,7 +54,7 @@ String getExpiryStr(String dateFormat, Duration offset) { public void testRefreshWithExpiry( String testName, int minutesUntilExpiry, boolean shouldBeExpired) throws IOException, InterruptedException { - for (String dateFormat : DATE_FORMATS) { + for (String dateFormat : getDateFormats()) { // Mock environment Environment env = mock(Environment.class); Map envMap = new HashMap<>(); From d1c1a6c791cedb2d338fbfeb4a82d909488a9847 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Thu, 12 Jun 2025 15:15:37 +0000 Subject: [PATCH 19/26] Generate date formats at run-time --- .../sdk/core/CliTokenSourceTest.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 54e762c91..a7202210b 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -34,13 +34,15 @@ import org.mockito.MockedStatic; public class CliTokenSourceTest { - private static final String[] DATE_FORMATS = { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - TimeZone.getDefault().getID().equals("UTC") - ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - }; + private String[] getDateFormats() { + return new String[] { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + TimeZone.getDefault().getID().equals("UTC") + ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + }; + } String getExpiryStr(String dateFormat, Duration offset) { ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset); @@ -50,7 +52,7 @@ String getExpiryStr(String dateFormat, Duration offset) { public void testRefreshWithExpiry( String testName, int minutesUntilExpiry, boolean shouldBeExpired) throws IOException, InterruptedException { - for (String dateFormat : DATE_FORMATS) { + for (String dateFormat : getDateFormats()) { // Mock environment Environment env = mock(Environment.class); Map envMap = new HashMap<>(); From 785c4009bb1362bfe4534cad7b6836a9f9b19ed7 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 09:13:03 +0000 Subject: [PATCH 20/26] Update stream of test cases --- .../sdk/core/CliTokenSourceTest.java | 125 +++++++++--------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index a7202210b..0614d11c7 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -18,6 +18,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -34,25 +35,77 @@ import org.mockito.MockedStatic; public class CliTokenSourceTest { - private String[] getDateFormats() { - return new String[] { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - TimeZone.getDefault().getID().equals("UTC") - ? "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - : "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" - }; - } - String getExpiryStr(String dateFormat, Duration offset) { ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset); return futureExpiry.format(DateTimeFormatter.ofPattern(dateFormat)); } + private static Stream provideTimezoneTestCases() { + // Generate timezones from GMT-12 to GMT+12 + List timezones = + IntStream.rangeClosed(-12, 12) + .mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset)) + .collect(Collectors.toList()); + + // Time to expiry of tokens (minutes, shouldBeExpired) + List minutesUntilExpiry = + Arrays.asList( + Arguments.of(5, false), // 5 minutes remaining + Arguments.of(30, false), // 30 minutes remaining + Arguments.of(60, false), // 1 hour remaining + Arguments.of(120, false), // 2 hours remaining + Arguments.of(-5, true), // 5 minutes ago + Arguments.of(-30, true), // 30 minutes ago + Arguments.of(-60, true), // 1 hour ago + Arguments.of(-120, true) // 2 hours ago + ); + + // Create cross product of timezones and minutesUntilExpiry case and match the timezone with the + // date formats + return timezones.stream() + .flatMap( + timezone -> { + List dateFormats = + new ArrayList<>( + Arrays.asList( + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); + + if (timezone.equals("GMT")) { + dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + } + + return minutesUntilExpiry.stream() + .map( + minutesUntilExpiryCase -> { + Object[] args = minutesUntilExpiryCase.get(); + return Arguments.of(timezone, args[0], args[1], dateFormats); + }); + }); + } + + @ParameterizedTest(name = "Test in {0} with {1} minutes offset") + @MethodSource("provideTimezoneTestCases") + public void testRefreshWithDifferentTimezone( + String timezone, int minutesUntilExpiry, boolean shouldBeExpired, List dateFormats) + throws IOException, InterruptedException { + // Save original timezone + TimeZone originalTimeZone = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone(timezone)); + testRefreshWithExpiry( + "Test in " + timezone, minutesUntilExpiry, shouldBeExpired, dateFormats); + } finally { + // Restore original timezone + TimeZone.setDefault(originalTimeZone); + } + } + public void testRefreshWithExpiry( - String testName, int minutesUntilExpiry, boolean shouldBeExpired) + String testName, int minutesUntilExpiry, boolean shouldBeExpired, List dateFormats) throws IOException, InterruptedException { - for (String dateFormat : getDateFormats()) { + for (String dateFormat : dateFormats) { // Mock environment Environment env = mock(Environment.class); Map envMap = new HashMap<>(); @@ -102,54 +155,6 @@ public void testRefreshWithExpiry( } } - private static Stream provideTimezoneTestCases() { - // Generate timezones from GMT-12 to GMT+12 - List timezones = - IntStream.rangeClosed(-12, 12) - .mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset)) - .collect(Collectors.toList()); - - // Time to expiry of tokens (minutes, shouldBeExpired) - List minutesUntilExpiry = - Arrays.asList( - Arguments.of(5, false), // 5 minutes remaining - Arguments.of(30, false), // 30 minutes remaining - Arguments.of(60, false), // 1 hour remaining - Arguments.of(120, false), // 2 hours remaining - Arguments.of(-5, true), // 5 minutes ago - Arguments.of(-30, true), // 30 minutes ago - Arguments.of(-60, true), // 1 hour ago - Arguments.of(-120, true) // 2 hours ago - ); - - // Create cross product of timezones and minutesUntilExpiry cases - return timezones.stream() - .flatMap( - timezone -> - minutesUntilExpiry.stream() - .map( - minutesUntilExpiryCase -> { - Object[] args = minutesUntilExpiryCase.get(); - return Arguments.of(timezone, args[0], args[1]); - })); - } - - @ParameterizedTest(name = "Test in {0} with {1} minutes offset") - @MethodSource("provideTimezoneTestCases") - public void testRefreshWithDifferentTimezone( - String timezone, int minutesUntilExpiry, boolean shouldBeExpired) - throws IOException, InterruptedException { - // Save original timezone - TimeZone originalTimeZone = TimeZone.getDefault(); - try { - TimeZone.setDefault(TimeZone.getTimeZone(timezone)); - testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired); - } finally { - // Restore original timezone - TimeZone.setDefault(originalTimeZone); - } - } - @Test public void testParseExpiryWithoutTruncate() { LocalDateTime parsedDateTime = CliTokenSource.parseExpiry("2023-07-17T09:02:22.330612218Z"); From 9d5c85e220fb0e7c7ac26decb32eb259746be1b9 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 11:21:12 +0000 Subject: [PATCH 21/26] Add comment --- .../test/java/com/databricks/sdk/core/CliTokenSourceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index f7364c306..29229dc5c 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -75,6 +75,7 @@ private static Stream provideTimezoneTestCases() { "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); if (timezone.equals("GMT")) { + // We only test with this format when timezone is GMT dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } From 2a7cd9544662d2b9cd8e12e02c4e8d6d3c506bc8 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 11:22:35 +0000 Subject: [PATCH 22/26] Add comment --- .../test/java/com/databricks/sdk/core/CliTokenSourceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 0614d11c7..2e06939e2 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -73,6 +73,7 @@ private static Stream provideTimezoneTestCases() { "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); if (timezone.equals("GMT")) { + // We only test with this format when timezone is GMT dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } From 257998c649fa181ffa83f38d7f02dc1076d37506 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 12:23:08 +0000 Subject: [PATCH 23/26] Update comment --- .../java/com/databricks/sdk/core/CliTokenSourceTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 2e06939e2..b8108dc02 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -73,7 +73,11 @@ private static Stream provideTimezoneTestCases() { "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); if (timezone.equals("GMT")) { - // We only test with this format when timezone is GMT + /* + * The Databricks CLI outputs timestamps with 'Z' suffix (e.g., + * 2024-03-14T10:30:00.000Z) only when in UTC/GMT+0 timezone. + * Thus, we only test with this format together with the GMT timezone. + */ dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } From ceea58a329db26e705221dd43016778aa3f07fab Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 12:42:29 +0000 Subject: [PATCH 24/26] update stream of tests --- .../sdk/core/CliTokenSourceTest.java | 117 +++++++++--------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 89b82c2e3..63940b968 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -62,8 +62,7 @@ private static Stream provideTimezoneTestCases() { Arguments.of(-120, true) // 2 hours ago ); - // Create cross product of timezones and minutesUntilExpiry case and match the timezone with the - // date formats + // Create cross product of timezones and minutesUntilExpiry case return timezones.stream() .flatMap( timezone -> { @@ -83,26 +82,28 @@ private static Stream provideTimezoneTestCases() { dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } - return minutesUntilExpiry.stream() - .map( - minutesUntilExpiryCase -> { - Object[] args = minutesUntilExpiryCase.get(); - return Arguments.of(timezone, args[0], args[1], dateFormats); - }); + return dateFormats.stream() + .flatMap( + dateFormat -> + minutesUntilExpiry.stream() + .map( + minutesUntilExpiryCase -> { + Object[] args = minutesUntilExpiryCase.get(); + return Arguments.of(timezone, args[0], args[1], dateFormat); + })); }); } - @ParameterizedTest(name = "Test in {0} with {1} minutes offset") + @ParameterizedTest(name = "Test in {0} with {1} minutes offset using format {3}") @MethodSource("provideTimezoneTestCases") public void testRefreshWithDifferentTimezone( - String timezone, int minutesUntilExpiry, boolean shouldBeExpired, List dateFormats) + String timezone, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat) throws IOException, InterruptedException { // Save original timezone TimeZone originalTimeZone = TimeZone.getDefault(); try { TimeZone.setDefault(TimeZone.getTimeZone(timezone)); - testRefreshWithExpiry( - "Test in " + timezone, minutesUntilExpiry, shouldBeExpired, dateFormats); + testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired, dateFormat); } finally { // Restore original timezone TimeZone.setDefault(originalTimeZone); @@ -110,54 +111,52 @@ public void testRefreshWithDifferentTimezone( } public void testRefreshWithExpiry( - String testName, int minutesUntilExpiry, boolean shouldBeExpired, List dateFormats) + String testName, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat) throws IOException, InterruptedException { - for (String dateFormat : dateFormats) { - // Mock environment - Environment env = mock(Environment.class); - Map envMap = new HashMap<>(); - when(env.getEnv()).thenReturn(envMap); - - // Create test command - List cmd = Arrays.asList("test", "command"); - - // Mock OSUtilities - OSUtilities osUtils = mock(OSUtilities.class); - when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); - - try (MockedStatic mockedOSUtils = mockStatic(OSUtils.class)) { - mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils); - - CliTokenSource tokenSource = - new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); - - String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); - - // Mock process to return the specified expiry string - Process process = mock(Process.class); - when(process.getInputStream()) - .thenReturn( - new ByteArrayInputStream( - String.format( - "{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", - expiryStr) - .getBytes())); - when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); - when(process.waitFor()).thenReturn(0); - - // Mock ProcessBuilder constructor - try (MockedConstruction mocked = - mockConstruction( - ProcessBuilder.class, - (mock, context) -> { - when(mock.start()).thenReturn(process); - })) { - // Test refresh - Token token = tokenSource.refresh(); - assertEquals("Bearer", token.getTokenType()); - assertEquals("test-token", token.getAccessToken()); - assertEquals(shouldBeExpired, token.isExpired()); - } + // Mock environment + Environment env = mock(Environment.class); + Map envMap = new HashMap<>(); + when(env.getEnv()).thenReturn(envMap); + + // Create test command + List cmd = Arrays.asList("test", "command"); + + // Mock OSUtilities + OSUtilities osUtils = mock(OSUtilities.class); + when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); + + try (MockedStatic mockedOSUtils = mockStatic(OSUtils.class)) { + mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils); + + CliTokenSource tokenSource = + new CliTokenSource(cmd, "token_type", "access_token", "expiry", env); + + String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); + + // Mock process to return the specified expiry string + Process process = mock(Process.class); + when(process.getInputStream()) + .thenReturn( + new ByteArrayInputStream( + String.format( + "{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}", + expiryStr) + .getBytes())); + when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); + when(process.waitFor()).thenReturn(0); + + // Mock ProcessBuilder constructor + try (MockedConstruction mocked = + mockConstruction( + ProcessBuilder.class, + (mock, context) -> { + when(mock.start()).thenReturn(process); + })) { + // Test refresh + Token token = tokenSource.refresh(); + assertEquals("Bearer", token.getTokenType()); + assertEquals("test-token", token.getAccessToken()); + assertEquals(shouldBeExpired, token.isExpired()); } } } From 0697faef8af01a647cd82e4e9cedb243b4beb3d2 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Fri, 13 Jun 2025 14:09:23 +0000 Subject: [PATCH 25/26] Polish comments --- .../sdk/core/CliTokenSourceTest.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java index 63940b968..aa98730cb 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java @@ -43,13 +43,13 @@ String getExpiryStr(String dateFormat, Duration offset) { } private static Stream provideTimezoneTestCases() { - // Generate timezones from GMT-12 to GMT+12 + // Generate timezones from GMT-12 to GMT+12. List timezones = IntStream.rangeClosed(-12, 12) .mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset)) .collect(Collectors.toList()); - // Time to expiry of tokens (minutes, shouldBeExpired) + // Time to expiry of tokens (minutes, shouldBeExpired). List minutesUntilExpiry = Arrays.asList( Arguments.of(5, false), // 5 minutes remaining @@ -62,7 +62,7 @@ private static Stream provideTimezoneTestCases() { Arguments.of(-120, true) // 2 hours ago ); - // Create cross product of timezones and minutesUntilExpiry case + // Create cross product of timezones and minutesUntilExpiry cases. return timezones.stream() .flatMap( timezone -> { @@ -74,11 +74,9 @@ private static Stream provideTimezoneTestCases() { "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); if (timezone.equals("GMT")) { - /* - * The Databricks CLI outputs timestamps with 'Z' suffix (e.g., - * 2024-03-14T10:30:00.000Z) only when in UTC/GMT+0 timezone. - * Thus, we only test with this format together with the GMT timezone. - */ + // The Databricks CLI outputs timestamps with 'Z' suffix (e.g., + // 2024-03-14T10:30:00.000Z) only when in UTC/GMT+0 timezone. + // Thus, we only test with this format together with the GMT timezone. dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); } @@ -99,13 +97,13 @@ private static Stream provideTimezoneTestCases() { public void testRefreshWithDifferentTimezone( String timezone, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat) throws IOException, InterruptedException { - // Save original timezone + // Save original timezone. TimeZone originalTimeZone = TimeZone.getDefault(); try { TimeZone.setDefault(TimeZone.getTimeZone(timezone)); testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired, dateFormat); } finally { - // Restore original timezone + // Restore original timezone. TimeZone.setDefault(originalTimeZone); } } @@ -113,15 +111,15 @@ public void testRefreshWithDifferentTimezone( public void testRefreshWithExpiry( String testName, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat) throws IOException, InterruptedException { - // Mock environment + // Mock environment. Environment env = mock(Environment.class); Map envMap = new HashMap<>(); when(env.getEnv()).thenReturn(envMap); - // Create test command + // Create test command. List cmd = Arrays.asList("test", "command"); - // Mock OSUtilities + // Mock OSUtilities. OSUtilities osUtils = mock(OSUtilities.class); when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd); @@ -133,7 +131,7 @@ public void testRefreshWithExpiry( String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry)); - // Mock process to return the specified expiry string + // Mock process to return the specified expiry string. Process process = mock(Process.class); when(process.getInputStream()) .thenReturn( @@ -145,14 +143,14 @@ public void testRefreshWithExpiry( when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0])); when(process.waitFor()).thenReturn(0); - // Mock ProcessBuilder constructor + // Mock ProcessBuilder constructor. try (MockedConstruction mocked = mockConstruction( ProcessBuilder.class, (mock, context) -> { when(mock.start()).thenReturn(process); })) { - // Test refresh + // Test refresh. Token token = tokenSource.refresh(); assertEquals("Bearer", token.getTokenType()); assertEquals("test-token", token.getAccessToken()); From f58a2f6fde4881e0a9f651287d4a4261e7345343 Mon Sep 17 00:00:00 2001 From: emmyzhou-db Date: Mon, 16 Jun 2025 08:42:22 +0000 Subject: [PATCH 26/26] Improve Javadoc --- .../java/com/databricks/sdk/core/CliTokenSource.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index ff0740caa..17e409d93 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -42,12 +42,13 @@ public CliTokenSource( /** * Parses an expiry time string and returns the corresponding {@link Instant}. The method attempts * to parse the input in the following order: 1. RFC 3339/ISO 8601 format with offset (e.g. - * "2024-03-20T10:30:00Z") 2. Local date-time format "yyyy-MM-dd HH:mm:ss" (e.g. "2024-03-20 - * 10:30:00") 3. Local date-time format with optional fractional seconds of varying precision - * (e.g. "2024-03-20 10:30:00.123") + * "2024-03-20T10:30:00Z") - If the timestamp is compliant with ISO 8601 and RFC 3339, the + * timezone indicator (Z or +/-HH:mm) is used as part of the conversion process to UTC 2. Local + * date-time format "yyyy-MM-dd HH:mm:ss" (e.g. "2024-03-20 10:30:00") 3. Local date-time format + * with optional fractional seconds of varying precision (e.g. "2024-03-20 10:30:00.123") * - *

Any specified time zone or offset is converted to UTC. For local date-time formats, the - * system's default time zone is used. + *

For timestamps without a timezone indicator (formats 2 and 3), the system's default time + * zone is assumed and used for the conversion to UTC. * * @param expiry expiry time string in one of the supported formats * @return the parsed {@link Instant}