Skip to content

Commit 7cfabdf

Browse files
committed
Implement a fail safe way to ensure the system clock has advanced enough to ensure the time has changed
This can later be refactored to using a TimeService with manual advancement, as to not have delays
1 parent a8cd0b2 commit 7cfabdf

File tree

1 file changed

+21
-5
lines changed

1 file changed

+21
-5
lines changed

uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.cloudfoundry.identity.uaa.user.UaaUser;
4848
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
4949
import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator;
50+
import org.cloudfoundry.identity.uaa.util.TimeService;
5051
import org.cloudfoundry.identity.uaa.util.JsonUtils;
5152
import org.cloudfoundry.identity.uaa.util.SessionUtils;
5253
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneResolutionMode;
@@ -1023,25 +1024,40 @@ void clientIdentityProviderWithoutAllowedProvidersForPasswordGrantWorksInOtherZo
10231024

10241025
@Test
10251026
void getToken_withPasswordGrantType_resultsInUserLastLogonTimestampUpdate() throws Exception {
1026-
long delayTime = 15;
1027+
TimeService timeService = webApplicationContext.getBean(TimeService.class);
1028+
long delayTime = 2;
10271029
String username = "testuser" + generator.generate();
10281030
String userScopes = "uaa.user";
10291031
ScimUser user = setUpUser(jdbcScimUserProvisioning, jdbcScimGroupMembershipManager, jdbcScimGroupProvisioning, username, userScopes, OriginKeys.UAA, IdentityZone.getUaaZoneId());
10301032
webApplicationContext.getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId());
10311033
webApplicationContext.getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId());
1032-
1034+
// On a fast processor there isn't enough granularity in the time; ensure the clock has moved
1035+
// before each password grant so we get distinct last-logon timestamps. Only sleep when needed.
1036+
// Use the same TimeService as production so we observe the same clock.
1037+
long afterSetup = timeService.getCurrentTimeMillis();
1038+
ensureClockMoved(timeService, afterSetup, delayTime);
10331039
String accessToken = getAccessTokenForPasswordGrant(username);
10341040
Long firstTimestamp = getPreviousLogonTime(accessToken);
1035-
//simulate two sequential tests
1036-
//on a fast processor, there isn't enough granularity in the time
1037-
Thread.sleep(delayTime);
1041+
long afterFirstGrant = timeService.getCurrentTimeMillis();
1042+
ensureClockMoved(timeService, afterFirstGrant, delayTime);
10381043
String accessToken2 = getAccessTokenForPasswordGrant(username);
10391044
Long secondTimestamp = getPreviousLogonTime(accessToken2);
10401045

10411046
assertThat(secondTimestamp).isNotEqualTo(firstTimestamp);
10421047
assertThat(firstTimestamp).isLessThan(secondTimestamp);
10431048
}
10441049

1050+
/**
1051+
* Waits until the application's {@link TimeService} clock has advanced past {@code notBefore}.
1052+
* Only sleeps when the clock has not yet moved, so fast runs avoid unnecessary delay.
1053+
* Uses the same TimeService as production (e.g. last-logon updates) so we observe the same clock.
1054+
*/
1055+
private void ensureClockMoved(TimeService timeService, long notBefore, long sleepMs) throws InterruptedException {
1056+
while (timeService.getCurrentTimeMillis() <= notBefore) {
1057+
Thread.sleep(sleepMs);
1058+
}
1059+
}
1060+
10451061
private String getAccessTokenForPasswordGrant(String username) throws Exception {
10461062
String response = mockMvc.perform(
10471063
post("/oauth/token")

0 commit comments

Comments
 (0)