11/**
2- * (C) Copyright IBM Corp. 2015, 2023 .
2+ * (C) Copyright IBM Corp. 2015, 2024 .
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55 * the License. You may obtain a copy of the License at
@@ -281,8 +281,9 @@ public void testSetScope() {
281281 public void testAuthenticateNewAndStoredToken () throws Throwable {
282282 server .enqueue (jsonResponse (tokenData ));
283283
284- // Mock current time to ensure that we're way before the token expiration time.
285- clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn ((long ) 100 );
284+ // Mock current time to ensure that we're way before the first token's expiration time.
285+ // This is because initially we have no access token at all so we should fetch one regardless of the current time.
286+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (0L );
286287
287288 IamAuthenticator authenticator = new IamAuthenticator .Builder ()
288289 .apikey (API_KEY )
@@ -317,6 +318,9 @@ public void testAuthenticateNewAndStoredToken() throws Throwable {
317318 Headers actualHeaders = tokenServerRequest .getHeaders ();
318319 assertNull (actualHeaders .get (HttpHeaders .AUTHORIZATION ));
319320
321+ // Set mock time to be within tokenData's lifetime, but before the refresh/expiration times.
322+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (tokenData .getExpiration () - 1000L );
323+
320324 // Authenticator should just return the same token this time since we have a valid one stored.
321325 requestBuilder = new Request .Builder ().url ("https://test.com" );
322326 authenticator .authenticate (requestBuilder );
@@ -330,17 +334,24 @@ public void testAuthenticateNewAndStoredToken() throws Throwable {
330334 public void testAuthenticationExpiredToken () {
331335 server .enqueue (jsonResponse (tokenData ));
332336
333- // Mock current time to ensure that we've passed the token expiration time.
334- clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn ((long ) 1800000000 );
337+ // Mock current time to ensure that we're way before the first token's expiration time.
338+ // This is because initially we have no access token at all so we should fetch one regardless of the current time.
339+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (0L );
335340
336- IamAuthenticator authenticator = new IamAuthenticator (API_KEY );
337- authenticator .setURL (url );
341+ IamAuthenticator authenticator = new IamAuthenticator .Builder ()
342+ .apikey (API_KEY )
343+ .url (url )
344+ .build ();
338345
339346 Request .Builder requestBuilder = new Request .Builder ().url ("https://test.com" );
340347
341348 // This will bootstrap the test by forcing the Authenticator to store the expired token
342349 // set above in the mock server.
343350 authenticator .authenticate (requestBuilder );
351+ verifyAuthHeader (requestBuilder , "Bearer " + tokenData .getAccessToken ());
352+
353+ // Now set the mock time to reflect that the first access token ("tokenData") has expired.
354+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (tokenData .getExpiration ());
344355
345356 // Authenticator should detect the expiration and request a new access token when we call authenticate() again.
346357 server .enqueue (jsonResponse (refreshedTokenData ));
@@ -349,21 +360,59 @@ public void testAuthenticationExpiredToken() {
349360 }
350361
351362 @ Test
352- public void testAuthenticationBackgroundTokenRefresh () throws InterruptedException {
363+ public void testAuthenticationExpiredToken10SecWindow () {
353364 server .enqueue (jsonResponse (tokenData ));
354365
355- // Mock current time to put us in the "refresh window" where the token is not expired but still needs refreshed.
356- clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn ((long ) 1522788600 );
366+ // Set initial mock time to be epoch time.
367+ // This is because initially we have no access token at all so we should fetch one regardless of the current time.
368+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (0L );
357369
358- IamAuthenticator authenticator = new IamAuthenticator .Builder ().apikey (API_KEY ).build ();
359- authenticator .setURL (url );
370+ IamAuthenticator authenticator = new IamAuthenticator .Builder ()
371+ .apikey (API_KEY )
372+ .url (url )
373+ .build ();
360374
361375 Request .Builder requestBuilder = new Request .Builder ().url ("https://test.com" );
362376
363- // This will bootstrap the test by forcing the Authenticator to store the token needing refreshed, which was
377+ // This will bootstrap the test by forcing the Authenticator to store the expired token
364378 // set above in the mock server.
365379 authenticator .authenticate (requestBuilder );
366380
381+ // Now set the mock time to reflect that the first access token ("tokenData") is considered to be "expired".
382+ // We subtract 10s from the expiration time to test the boundary condition of the expiration window feature.
383+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (
384+ tokenData .getExpiration () - IamToken .IamExpirationWindow );
385+
386+ // Authenticator should detect the expiration and request a new access token when we call authenticate() again.
387+ server .enqueue (jsonResponse (refreshedTokenData ));
388+ authenticator .authenticate (requestBuilder );
389+ verifyAuthHeader (requestBuilder , "Bearer " + refreshedTokenData .getAccessToken ());
390+ }
391+
392+ @ Test
393+ public void testAuthenticationBackgroundTokenRefresh () throws InterruptedException {
394+ server .enqueue (jsonResponse (tokenData ));
395+
396+ // Set initial mock time to be epoch time.
397+ // This is because initially we have no access token at all so we should fetch one regardless of the current time.
398+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (0L );
399+
400+ IamAuthenticator authenticator = new IamAuthenticator .Builder ()
401+ .apikey (API_KEY )
402+ .url (url )
403+ .build ();
404+
405+ Request .Builder requestBuilder = new Request .Builder ().url ("https://test.com" );
406+
407+ // This will bootstrap the test by forcing the Authenticator to store the first access token (tokenData).
408+ authenticator .authenticate (requestBuilder );
409+
410+ // Now set the mock time to put us in the "refresh window" where the token is not expired,
411+ // but still needs to be refreshed.
412+ long refreshWindow = (long ) (tokenData .getExpiresIn () * 0.2 );
413+ long refreshTime = tokenData .getExpiration () - refreshWindow ;
414+ clockMock .when (() -> Clock .getCurrentTimeInSeconds ()).thenReturn (refreshTime + 2L );
415+
367416 // Authenticator should detect the need to refresh and request a new access token IN THE BACKGROUND when we call
368417 // authenticate() again. The immediate response should be the token which was already stored, since it's not yet
369418 // expired.
@@ -391,7 +440,11 @@ public void testUserHeaders() throws Throwable {
391440 headers .put ("header1" , "value1" );
392441 headers .put ("header2" , "value2" );
393442 headers .put ("Host" , "iam.cloud.ibm.com:81" );
394- IamAuthenticator authenticator = new IamAuthenticator (API_KEY , url , null , null , false , headers );
443+ IamAuthenticator authenticator = new IamAuthenticator .Builder ()
444+ .apikey (API_KEY )
445+ .url (url )
446+ .headers (headers )
447+ .build ();
395448
396449 Request .Builder requestBuilder = new Request .Builder ().url ("https://test.com" );
397450
0 commit comments