Skip to content

Commit 772e9ea

Browse files
authored
test: initial refactor of OpenIDConnectionUtils tests
Signed-off-by: Marc Nuri <[email protected]>
1 parent 4ec7c99 commit 772e9ea

File tree

4 files changed

+126
-96
lines changed

4 files changed

+126
-96
lines changed

Diff for: kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@
3838
import java.security.cert.CertificateException;
3939
import java.security.spec.InvalidKeySpecException;
4040
import java.time.Instant;
41-
import java.util.*;
41+
import java.util.Base64;
42+
import java.util.Collections;
43+
import java.util.HashMap;
44+
import java.util.LinkedHashMap;
45+
import java.util.Map;
46+
import java.util.Optional;
4247
import java.util.concurrent.CompletableFuture;
4348
import java.util.function.Consumer;
4449

@@ -133,7 +138,6 @@ static boolean isTokenRefreshSupported(Map<String, String> currentAuthProviderCo
133138
* @param clientSecret client secret
134139
* @param tokenURL OpenID Connection provider's token refresh url
135140
* @return response as HashMap
136-
* @throws IOException in case of any error in contacting OIDC provider
137141
*/
138142
static CompletableFuture<Map<String, Object>> refreshOidcToken(HttpClient client, String clientId, String refreshToken,
139143
String clientSecret, String tokenURL) {
@@ -169,7 +173,7 @@ static CompletableFuture<Map<String, Object>> refreshOidcToken(HttpClient client
169173
* @return a HashMap of Discovery document
170174
*/
171175
static CompletableFuture<Map<String, Object>> getOIDCDiscoveryDocumentAsMap(HttpClient client, String issuer) {
172-
HttpRequest request = client.newHttpRequestBuilder().uri(getWellKnownUrlForOpenIDIssuer(issuer)).build();
176+
HttpRequest request = client.newHttpRequestBuilder().uri(resolveWellKnownUrlForOpenIDIssuer(issuer)).build();
173177
return client.sendAsync(request, String.class).thenApply(response -> {
174178
try {
175179
if (response.isSuccessful() && response.body() != null) {
@@ -193,7 +197,7 @@ static CompletableFuture<Map<String, Object>> getOIDCDiscoveryDocumentAsMap(Http
193197
* @param issuer issuing authority URL
194198
* @return well known URL for corresponding OpenID provider
195199
*/
196-
static String getWellKnownUrlForOpenIDIssuer(String issuer) {
200+
private static String resolveWellKnownUrlForOpenIDIssuer(String issuer) {
197201
return URLUtils.join(issuer, "/", WELL_KNOWN_OPENID_CONFIGURATION);
198202
}
199203

Diff for: kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/TestStandardHttpClientBuilder.java

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ protected TestStandardHttpClientBuilder(TestStandardHttpClientFactory clientFact
3232

3333
@Override
3434
public TestStandardHttpClient build() {
35+
if (clientFactory.getMode() == TestStandardHttpClientFactory.Mode.SINGLETON && instances.size() == 1) {
36+
return instances.peek();
37+
}
3538
final TestStandardHttpClient instance = new TestStandardHttpClient(this,
3639
Optional.ofNullable(instances.peek()).map(TestStandardHttpClient::getClosed).orElse(new AtomicBoolean()));
3740
instances.add(instance);

Diff for: kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/TestStandardHttpClientFactory.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,36 @@
2121
import java.util.stream.IntStream;
2222
import java.util.stream.Stream;
2323

24+
@Getter
2425
public class TestStandardHttpClientFactory implements HttpClient.Factory {
2526

26-
@Getter
27-
private final ConcurrentLinkedQueue<TestStandardHttpClient> instances = new ConcurrentLinkedQueue<>();
27+
public enum Mode {
28+
/**
29+
* A new instance of the HttpClient is created for each build.
30+
*/
31+
MULTIPLE,
32+
/**
33+
* The factory and builder share a single instance of the HttpClient whenever it's built.
34+
* Useful for mocking or setting expectations before a client is built using the factory's builder.
35+
*/
36+
SINGLETON
37+
}
38+
39+
private final Mode mode;
40+
private final ConcurrentLinkedQueue<TestStandardHttpClient> instances;
41+
42+
public TestStandardHttpClientFactory() {
43+
this(Mode.MULTIPLE);
44+
}
45+
46+
public TestStandardHttpClientFactory(Mode mode) {
47+
this.mode = mode == null ? Mode.MULTIPLE : mode;
48+
instances = new ConcurrentLinkedQueue<>();
49+
if (mode == Mode.SINGLETON) {
50+
// Create the singleton instance (will be automatically added to the instances queue)
51+
newBuilder().build();
52+
}
53+
}
2854

2955
@Override
3056
public TestStandardHttpClientBuilder newBuilder() {

Diff for: kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtilsTest.java

+87-90
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@
1515
*/
1616
package io.fabric8.kubernetes.client.utils;
1717

18+
import io.fabric8.kubernetes.api.model.AuthProviderConfig;
1819
import io.fabric8.kubernetes.api.model.AuthProviderConfigBuilder;
1920
import io.fabric8.kubernetes.api.model.NamedContext;
2021
import io.fabric8.kubernetes.client.Config;
2122
import io.fabric8.kubernetes.client.ConfigBuilder;
22-
import io.fabric8.kubernetes.client.http.HttpClient;
23-
import io.fabric8.kubernetes.client.http.HttpResponse;
23+
import io.fabric8.kubernetes.client.http.TestStandardHttpClient;
24+
import io.fabric8.kubernetes.client.http.TestStandardHttpClientBuilder;
25+
import io.fabric8.kubernetes.client.http.TestStandardHttpClientFactory;
2426
import io.fabric8.kubernetes.client.internal.KubeConfigUtils;
2527
import io.fabric8.kubernetes.client.internal.SSLUtils;
28+
import org.junit.jupiter.api.BeforeEach;
2629
import org.junit.jupiter.api.Test;
2730
import org.junit.jupiter.api.io.TempDir;
2831
import org.mockito.MockedStatic;
29-
import org.mockito.Mockito;
3032

3133
import java.io.File;
3234
import java.io.FileInputStream;
3335
import java.io.IOException;
34-
import java.net.HttpURLConnection;
3536
import java.nio.charset.StandardCharsets;
3637
import java.nio.file.Files;
3738
import java.nio.file.Paths;
@@ -40,8 +41,8 @@
4041
import java.util.Base64;
4142
import java.util.HashMap;
4243
import java.util.Map;
43-
import java.util.concurrent.CompletableFuture;
4444

45+
import static io.fabric8.kubernetes.client.http.TestStandardHttpClientFactory.Mode.SINGLETON;
4546
import static io.fabric8.kubernetes.client.utils.OpenIDConnectionUtils.CLIENT_ID_KUBECONFIG;
4647
import static io.fabric8.kubernetes.client.utils.OpenIDConnectionUtils.CLIENT_SECRET_KUBECONFIG;
4748
import static io.fabric8.kubernetes.client.utils.OpenIDConnectionUtils.ID_TOKEN_KUBECONFIG;
@@ -55,111 +56,98 @@
5556
import static org.junit.jupiter.api.Assertions.assertFalse;
5657
import static org.junit.jupiter.api.Assertions.assertNotNull;
5758
import static org.junit.jupiter.api.Assertions.assertTrue;
58-
import static org.mockito.ArgumentMatchers.any;
5959
import static org.mockito.ArgumentMatchers.anyBoolean;
6060
import static org.mockito.ArgumentMatchers.eq;
6161
import static org.mockito.ArgumentMatchers.isNull;
62-
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
63-
import static org.mockito.Mockito.mock;
6462
import static org.mockito.Mockito.mockStatic;
65-
import static org.mockito.Mockito.when;
6663

6764
class OpenIDConnectionUtilsTest {
68-
HttpClient mockClient = mock(HttpClient.class, Mockito.RETURNS_DEEP_STUBS);
6965

70-
@Test
71-
void testLoadTokenURL() throws Exception {
72-
// Given
73-
String openIdIssuer = "https://accounts.example.com";
74-
String tokenEndpointResponse = "{\"issuer\": \"https://accounts.example.com\"," +
75-
" \"token_endpoint\": \"https://oauth2.exampleapis.com/token\"}";
76-
mockHttpClient(HttpURLConnection.HTTP_OK, tokenEndpointResponse);
77-
78-
// When
79-
Map<String, Object> discoveryDocumentMap = OpenIDConnectionUtils.getOIDCDiscoveryDocumentAsMap(mockClient, openIdIssuer)
80-
.get();
66+
private TestStandardHttpClient httpClient;
67+
private TestStandardHttpClientBuilder singletonHttpClientBuilder;
8168

82-
// Then
83-
assertNotNull(discoveryDocumentMap);
84-
assertEquals("https://oauth2.exampleapis.com/token", discoveryDocumentMap.get(TOKEN_ENDPOINT_PARAM));
69+
@BeforeEach
70+
void setUp() {
71+
final TestStandardHttpClientFactory factory = new TestStandardHttpClientFactory(SINGLETON);
72+
httpClient = factory.newBuilder().build();
73+
singletonHttpClientBuilder = factory.newBuilder();
8574
}
8675

8776
@Test
88-
void testLoadTokenURLWhenNotFound() throws Exception {
77+
void loadTokenURL() throws Exception {
8978
// Given
90-
String openIdIssuer = "https://accounts.example.com";
91-
String tokenEndpointResponse = "{}";
92-
mockHttpClient(HttpURLConnection.HTTP_NOT_FOUND, tokenEndpointResponse);
93-
79+
httpClient.expect("/.well-known/openid-configuration", 200,
80+
"{\"issuer\": \"https://accounts.example.com\",\"token_endpoint\": \"https://oauth2.exampleapis.com/token\"}");
9481
// When
95-
Map<String, Object> discoveryDocumentAsMap = OpenIDConnectionUtils.getOIDCDiscoveryDocumentAsMap(mockClient, openIdIssuer)
82+
Map<String, Object> result = OpenIDConnectionUtils.getOIDCDiscoveryDocumentAsMap(httpClient, "https://accounts.example.com")
9683
.get();
97-
9884
// Then
99-
assertTrue(discoveryDocumentAsMap.isEmpty());
85+
assertThat(result)
86+
.isNotNull()
87+
.containsEntry("token_endpoint", "https://oauth2.exampleapis.com/token");
10088
}
10189

10290
@Test
103-
void testGetWellKnownUrlForOpenIDIssuer() {
91+
void loadTokenURLWhenNotFound() throws Exception {
10492
// Given
105-
String openIdIssuer = "https://accounts.example.com";
106-
93+
httpClient.expect("/.well-known/openid-configuration", 404);
10794
// When
108-
String wellKnownUrl = OpenIDConnectionUtils.getWellKnownUrlForOpenIDIssuer(openIdIssuer);
109-
95+
Map<String, Object> result = OpenIDConnectionUtils.getOIDCDiscoveryDocumentAsMap(httpClient, "https://accounts.example.com")
96+
.get();
11097
// Then
111-
assertEquals("https://accounts.example.com/.well-known/openid-configuration", wellKnownUrl);
98+
assertThat(result).isEmpty();
11299
}
113100

114101
@Test
115-
void testRefreshOidcToken() throws Exception {
102+
void refreshOidcToken() throws Exception {
116103
// Given
117104
String clientId = "test-client-id";
118105
String refreshToken = "test-refresh-token";
119106
String clientSecret = "test-client-secret";
120107
String tokenEndpointUrl = "https://oauth2.exampleapis.com/token";
121-
mockHttpClient(HttpURLConnection.HTTP_OK,
122-
"{\"" + ID_TOKEN_PARAM + "\":\"thisisatesttoken\",\"access_token\": \"thisisrefreshtoken\"," +
123-
"\"expires_in\": 3599," +
124-
"\"scope\": \"openid https://www.exampleapis.com/auth/userinfo.email\"," +
125-
"\"token_type\": \"Bearer\"}");
126-
108+
httpClient.expect("/token", 200, "{" +
109+
"\"id_token\":\"thisisatesttoken\"," +
110+
"\"access_token\":\"thisisrefreshtoken\"," +
111+
"\"expires_in\":3599," +
112+
"\"scope\":\"openid https://www.exampleapis.com/auth/userinfo.email\"," +
113+
"\"token_type\":\"Bearer\"" +
114+
"}");
127115
// When
128-
Map<String, Object> response = OpenIDConnectionUtils
129-
.refreshOidcToken(mockClient, clientId, refreshToken, clientSecret, tokenEndpointUrl).get();
130-
116+
Map<String, Object> result = OpenIDConnectionUtils
117+
.refreshOidcToken(httpClient, clientId, refreshToken, clientSecret, tokenEndpointUrl).get();
131118
// Then
132-
assertNotNull(response);
133-
assertEquals("thisisatesttoken", response.get(ID_TOKEN_PARAM));
119+
assertThat(result)
120+
.isNotNull()
121+
.containsEntry("id_token", "thisisatesttoken");
134122
}
135123

136124
@Test
137-
void testFetchOIDCProviderDiscoveryDocumentAndRefreshToken() throws Exception {
125+
void fetchOIDCProviderDiscoveryDocumentAndRefreshToken() throws Exception {
138126
// Given
139127
Map<String, Object> discoveryDocument = new HashMap<>();
140-
discoveryDocument.put(TOKEN_ENDPOINT_PARAM, "https://oauth2.exampleapis.com/token");
128+
discoveryDocument.put("token_endpoint", "https://oauth2.exampleapis.com/token");
141129
String clientId = "test-client-id";
142130
String refreshToken = "test-refresh-token";
143131
String clientSecret = "test-client-secret";
144-
mockHttpClient(HttpURLConnection.HTTP_OK,
145-
"{\"" + ID_TOKEN_PARAM + "\":\"thisisatesttoken\",\"access_token\": \"thisisrefreshtoken\"," +
146-
"\"expires_in\": 3599," +
147-
"\"scope\": \"openid https://www.exampleapis.com/auth/userinfo.email\"," +
148-
"\"token_type\": \"Bearer\"}");
149-
132+
httpClient.expect("/token", 200, "{" +
133+
"\"id_token\":\"thisisatesttoken\"," +
134+
"\"access_token\":\"thisisrefreshtoken\"," +
135+
"\"expires_in\":3599," +
136+
"\"scope\":\"openid https://www.exampleapis.com/auth/userinfo.email\"," +
137+
"\"token_type\":\"Bearer\"" +
138+
"}");
150139
// When
151-
String newAccessToken = String.valueOf(OpenIDConnectionUtils.refreshOidcToken(mockClient,
140+
Map<String, Object> result = OpenIDConnectionUtils.refreshOidcToken(httpClient,
152141
clientId, refreshToken, clientSecret,
153-
OpenIDConnectionUtils.getParametersFromDiscoveryResponse(discoveryDocument, TOKEN_ENDPOINT_PARAM)).get()
154-
.get(ID_TOKEN_PARAM));
155-
142+
OpenIDConnectionUtils.getParametersFromDiscoveryResponse(discoveryDocument, "token_endpoint")).get();
156143
// Then
157-
assertNotNull(newAccessToken);
158-
assertEquals("thisisatesttoken", newAccessToken);
144+
assertThat(result)
145+
.isNotNull()
146+
.containsEntry("id_token", "thisisatesttoken");
159147
}
160148

161149
@Test
162-
void testPersistKubeConfigWithUpdatedToken() throws IOException {
150+
void persistKubeConfigWithUpdatedToken() throws IOException {
163151
// Given
164152
Map<String, Object> openIdProviderResponse = new HashMap<>();
165153
openIdProviderResponse.put(ID_TOKEN_PARAM, "id-token-updated");
@@ -196,7 +184,7 @@ void testPersistKubeConfigWithUpdatedToken() throws IOException {
196184
}
197185

198186
@Test
199-
void testResolveOIDCTokenFromAuthConfigShouldReturnOldTokenWhenRefreshNotSupported() throws Exception {
187+
void resolveOIDCTokenFromAuthConfigShouldReturnOldTokenWhenRefreshNotSupported() throws Exception {
200188
// Given
201189
Map<String, String> currentAuthProviderConfig = new HashMap<>();
202190
currentAuthProviderConfig.put(CLIENT_ID_KUBECONFIG, "client-id");
@@ -220,13 +208,24 @@ void resolveOIDCTokenFromAuthConfig_whenIDPCertNotPresentInAuthConfig_thenUseCer
220208
currentAuthProviderConfig.put(ID_TOKEN_KUBECONFIG, "id-token");
221209
currentAuthProviderConfig.put(REFRESH_TOKEN_KUBECONFIG, "refresh-token");
222210
currentAuthProviderConfig.put(ISSUER_KUBECONFIG, "https://iam.cloud.example.com/identity");
223-
Config config = new ConfigBuilder(Config.empty()).withCaCertData("cert").build();
224-
HttpClient.Builder builder = mock(HttpClient.Builder.class);
225-
HttpClient httpClient = mock(HttpClient.class, RETURNS_DEEP_STUBS);
226-
when(builder.build()).thenReturn(httpClient);
211+
Config config = new ConfigBuilder(Config.empty())
212+
.withCaCertData("cert")
213+
.withAuthProvider(new AuthProviderConfig())
214+
.build();
215+
httpClient.expect("/identity/.well-known/openid-configuration", 200, "{" +
216+
"\"issuer\":\"https://iam.cloud.example.com/identity\"," +
217+
"\"token_endpoint\":\"https://iam.cloud.example.com/identity/token\"" +
218+
"}");
219+
httpClient.expect("/identity/token", 200, "{" +
220+
"\"id_token\":\"thisisatesttoken\"," +
221+
"\"access_token\":\"thisisrefreshtoken\"," +
222+
"\"expires_in\":3599," +
223+
"\"scope\":\"openid https://www.exampleapis.com/auth/userinfo.email\"," +
224+
"\"token_type\":\"Bearer\"" +
225+
"}");
227226

228227
// When
229-
OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(config, currentAuthProviderConfig, builder).get();
228+
OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(config, currentAuthProviderConfig, singletonHttpClientBuilder).get();
230229

231230
// Then
232231
String decodedCert = new String(java.util.Base64.getDecoder().decode("cert"));
@@ -249,13 +248,24 @@ void resolveOIDCTokenFromAuthConfig_whenIDPCertNotPresentInAuthConfig_thenUseCer
249248
currentAuthProviderConfig.put(ID_TOKEN_KUBECONFIG, "id-token");
250249
currentAuthProviderConfig.put(REFRESH_TOKEN_KUBECONFIG, "refresh-token");
251250
currentAuthProviderConfig.put(ISSUER_KUBECONFIG, "https://iam.cloud.example.com/identity");
252-
Config config = new ConfigBuilder(Config.empty()).withCaCertFile(caCertFile.getAbsolutePath()).build();
253-
HttpClient.Builder builder = mock(HttpClient.Builder.class);
254-
HttpClient httpClient = mock(HttpClient.class, RETURNS_DEEP_STUBS);
255-
when(builder.build()).thenReturn(httpClient);
251+
Config config = new ConfigBuilder(Config.empty())
252+
.withCaCertFile(caCertFile.getAbsolutePath())
253+
.withAuthProvider(new AuthProviderConfig())
254+
.build();
255+
httpClient.expect("/identity/.well-known/openid-configuration", 200, "{" +
256+
"\"issuer\":\"https://iam.cloud.example.com/identity\"," +
257+
"\"token_endpoint\":\"https://iam.cloud.example.com/identity/token\"" +
258+
"}");
259+
httpClient.expect("/identity/token", 200, "{" +
260+
"\"id_token\":\"thisisatesttoken\"," +
261+
"\"access_token\":\"thisisrefreshtoken\"," +
262+
"\"expires_in\":3599," +
263+
"\"scope\":\"openid https://www.exampleapis.com/auth/userinfo.email\"," +
264+
"\"token_type\":\"Bearer\"" +
265+
"}");
256266

257267
// When
258-
OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(config, currentAuthProviderConfig, builder).get();
268+
OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(config, currentAuthProviderConfig, singletonHttpClientBuilder).get();
259269

260270
// Then
261271
sslUtilsMockedStatic.verify(() -> SSLUtils.trustManagers(eq("cert"), isNull(), anyBoolean(), isNull(), isNull()));
@@ -265,7 +275,7 @@ void resolveOIDCTokenFromAuthConfig_whenIDPCertNotPresentInAuthConfig_thenUseCer
265275
}
266276

267277
@Test
268-
void testgetParametersFromDiscoveryResponse() {
278+
void getParametersFromDiscoveryResponse() {
269279
// Given
270280
Map<String, Object> discoveryDocument = new HashMap<>();
271281
discoveryDocument.put("issuer", "https://api.login.example.com");
@@ -314,19 +324,6 @@ void idTokenExpired_whenTokenStillNotExpired_thenReturnFalse() {
314324
assertThat(OpenIDConnectionUtils.idTokenExpired(createNewConfigWithAuthProviderIdToken(token))).isFalse();
315325
}
316326

317-
private void mockHttpClient(int responseCode, String responseAsStr) throws IOException {
318-
HttpResponse<String> mockSuccessResponse = mockResponse(responseCode, responseAsStr);
319-
when(mockClient.sendAsync(any(), eq(String.class)))
320-
.thenReturn(CompletableFuture.completedFuture(mockSuccessResponse));
321-
}
322-
323-
private HttpResponse<String> mockResponse(int responseCode, String responseBody) {
324-
HttpResponse<String> response = mock(HttpResponse.class, Mockito.CALLS_REAL_METHODS);
325-
Mockito.when(response.code()).thenReturn(responseCode);
326-
Mockito.when(response.body()).thenReturn(responseBody);
327-
return response;
328-
}
329-
330327
private Config createNewConfigWithAuthProviderIdToken(String idToken) {
331328
return new ConfigBuilder(Config.empty())
332329
.withAuthProvider(new AuthProviderConfigBuilder()

0 commit comments

Comments
 (0)