Skip to content

Commit 7567b41

Browse files
authored
Add Additional Tests for CliTokenSource (#462)
## What changes are proposed in this pull request? This PR adds additional tests for the `CliTokenSource` class, specifically focusing on token expiration handling across different timezones and date formats. ## Key Changes This test verifies the behavior of the 'parseExpiry()' method in CliTokenSource by systematically testing tokens with varying expiration times (5, 30, 60, and 120 minutes before and after current system time) across all timezones. For each combination, it creates a mock token with the specified expiry time, temporarily sets the system timezone, and verifies that the token's expired status matches the expected value regardless of the timezone or date format used. **Important Note:** This test assumes that all expiry strings provided to `parseExpiry()` are in the system's local timezone. It does not cover scenarios where the expiry string is in a different timezone than where the SDK is running, as this would require additional timezone conversion logic that is not part of the current implementation. ## How is this tested? N/A NO_CHANGELOG=true
1 parent aec8727 commit 7567b41

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

databricks-sdk-java/src/test/java/com/databricks/sdk/core/CliTokenSourceTest.java

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,161 @@
11
package com.databricks.sdk.core;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.mockConstruction;
7+
import static org.mockito.Mockito.mockStatic;
8+
import static org.mockito.Mockito.when;
49

10+
import com.databricks.sdk.core.oauth.Token;
11+
import com.databricks.sdk.core.utils.Environment;
12+
import com.databricks.sdk.core.utils.OSUtilities;
13+
import com.databricks.sdk.core.utils.OSUtils;
14+
import java.io.ByteArrayInputStream;
15+
import java.io.IOException;
16+
import java.time.Duration;
517
import java.time.LocalDateTime;
18+
import java.time.ZonedDateTime;
19+
import java.time.format.DateTimeFormatter;
620
import java.time.format.DateTimeParseException;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.TimeZone;
27+
import java.util.stream.Collectors;
28+
import java.util.stream.IntStream;
29+
import java.util.stream.Stream;
730
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.params.ParameterizedTest;
32+
import org.junit.jupiter.params.provider.Arguments;
33+
import org.junit.jupiter.params.provider.MethodSource;
34+
import org.mockito.MockedConstruction;
35+
import org.mockito.MockedStatic;
836

937
public class CliTokenSourceTest {
38+
String getExpiryStr(String dateFormat, Duration offset) {
39+
ZonedDateTime futureExpiry = ZonedDateTime.now().plus(offset);
40+
return futureExpiry.format(DateTimeFormatter.ofPattern(dateFormat));
41+
}
42+
43+
private static Stream<Arguments> provideTimezoneTestCases() {
44+
// Generate timezones from GMT-12 to GMT+12.
45+
List<String> timezones =
46+
IntStream.rangeClosed(-12, 12)
47+
.mapToObj(offset -> offset == 0 ? "GMT" : String.format("GMT%+d", offset))
48+
.collect(Collectors.toList());
49+
50+
// Time to expiry of tokens (minutes, shouldBeExpired).
51+
List<Arguments> minutesUntilExpiry =
52+
Arrays.asList(
53+
Arguments.of(5, false), // 5 minutes remaining
54+
Arguments.of(30, false), // 30 minutes remaining
55+
Arguments.of(60, false), // 1 hour remaining
56+
Arguments.of(120, false), // 2 hours remaining
57+
Arguments.of(-5, true), // 5 minutes ago
58+
Arguments.of(-30, true), // 30 minutes ago
59+
Arguments.of(-60, true), // 1 hour ago
60+
Arguments.of(-120, true) // 2 hours ago
61+
);
62+
63+
// Create cross product of timezones and minutesUntilExpiry cases.
64+
return timezones.stream()
65+
.flatMap(
66+
timezone -> {
67+
List<String> dateFormats =
68+
new ArrayList<>(
69+
Arrays.asList(
70+
"yyyy-MM-dd HH:mm:ss",
71+
"yyyy-MM-dd HH:mm:ss.SSS",
72+
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
73+
74+
if (timezone.equals("GMT")) {
75+
// The Databricks CLI outputs timestamps with 'Z' suffix (e.g.,
76+
// 2024-03-14T10:30:00.000Z) only when in UTC/GMT+0 timezone.
77+
// Thus, we only test with this format together with the GMT timezone.
78+
dateFormats.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
79+
}
80+
81+
return dateFormats.stream()
82+
.flatMap(
83+
dateFormat ->
84+
minutesUntilExpiry.stream()
85+
.map(
86+
minutesUntilExpiryCase -> {
87+
Object[] args = minutesUntilExpiryCase.get();
88+
return Arguments.of(timezone, args[0], args[1], dateFormat);
89+
}));
90+
});
91+
}
92+
93+
@ParameterizedTest(name = "Test in {0} with {1} minutes offset using format {3}")
94+
@MethodSource("provideTimezoneTestCases")
95+
public void testRefreshWithDifferentTimezone(
96+
String timezone, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat)
97+
throws IOException, InterruptedException {
98+
// Save original timezone.
99+
TimeZone originalTimeZone = TimeZone.getDefault();
100+
try {
101+
TimeZone.setDefault(TimeZone.getTimeZone(timezone));
102+
testRefreshWithExpiry("Test in " + timezone, minutesUntilExpiry, shouldBeExpired, dateFormat);
103+
} finally {
104+
// Restore original timezone.
105+
TimeZone.setDefault(originalTimeZone);
106+
}
107+
}
108+
109+
public void testRefreshWithExpiry(
110+
String testName, int minutesUntilExpiry, boolean shouldBeExpired, String dateFormat)
111+
throws IOException, InterruptedException {
112+
// Mock environment.
113+
Environment env = mock(Environment.class);
114+
Map<String, String> envMap = new HashMap<>();
115+
when(env.getEnv()).thenReturn(envMap);
116+
117+
// Create test command.
118+
List<String> cmd = Arrays.asList("test", "command");
119+
120+
// Mock OSUtilities.
121+
OSUtilities osUtils = mock(OSUtilities.class);
122+
when(osUtils.getCliExecutableCommand(any())).thenReturn(cmd);
123+
124+
try (MockedStatic<OSUtils> mockedOSUtils = mockStatic(OSUtils.class)) {
125+
mockedOSUtils.when(() -> OSUtils.get(any())).thenReturn(osUtils);
126+
127+
CliTokenSource tokenSource =
128+
new CliTokenSource(cmd, "token_type", "access_token", "expiry", env);
129+
130+
String expiryStr = getExpiryStr(dateFormat, Duration.ofMinutes(minutesUntilExpiry));
131+
132+
// Mock process to return the specified expiry string.
133+
Process process = mock(Process.class);
134+
when(process.getInputStream())
135+
.thenReturn(
136+
new ByteArrayInputStream(
137+
String.format(
138+
"{\"token_type\": \"Bearer\", \"access_token\": \"test-token\", \"expiry\": \"%s\"}",
139+
expiryStr)
140+
.getBytes()));
141+
when(process.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0]));
142+
when(process.waitFor()).thenReturn(0);
143+
144+
// Mock ProcessBuilder constructor.
145+
try (MockedConstruction<ProcessBuilder> mocked =
146+
mockConstruction(
147+
ProcessBuilder.class,
148+
(mock, context) -> {
149+
when(mock.start()).thenReturn(process);
150+
})) {
151+
// Test refresh.
152+
Token token = tokenSource.refresh();
153+
assertEquals("Bearer", token.getTokenType());
154+
assertEquals("test-token", token.getAccessToken());
155+
assertEquals(shouldBeExpired, token.isExpired());
156+
}
157+
}
158+
}
10159

11160
@Test
12161
public void testParseExpiryWithoutTruncate() {

0 commit comments

Comments
 (0)