Repository: wso2-extensions/identity-inbound-auth-oauth
Pull request #2764: [Unit tests] Unit tests for refresh token expiry time
Author: login: indeewari
Created: 2025-04-19T10:44:01Z | Merged: 2025-04-19T15:04:44Z

Unit tests for refresh token expiry time

Parent issue

- https://github.com/wso2/product-is/issues/23835


Diff:
diff --git a/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java
new file mode 100644
index 0000000000..04f2c4cdb2
--- /dev/null
+++ b/components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
+ *
+ *  WSO2 LLC. licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except
+ *  in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.wso2.carbon.identity.oauth2.token.handlers;
+
+import org.testng.annotations.Test;
+import org.wso2.carbon.CarbonConstants;
+import org.wso2.carbon.context.PrivilegedCarbonContext;
+import org.wso2.carbon.identity.action.execution.api.model.ActionType;
+import org.wso2.carbon.identity.action.execution.api.service.ActionExecutorService;
+import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithH2Database;
+import org.wso2.carbon.identity.common.testng.WithRealmService;
+import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
+import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO;
+import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
+import static org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler.PREV_ACCESS_TOKEN;
+
+/**
+ * This test class covers the refresh token expiry time behavior in the OAuthTokenReqMessageContext.
+ */
+@WithCarbonHome
+@WithH2Database(jndiName = "jdbc/WSO2IdentityDB",
+        files = {"dbScripts/identity.sql", "dbScripts/insert_application_and_token_for_refresh_token_expiry.sql",
+                "dbScripts/insert_consumer_app.sql", "dbScripts/insert_local_idp.sql"})
+@WithRealmService(injectToSingletons = {OAuthComponentServiceHolder.class})
+public class RefreshTokenExpiryTest {
+
+    // Refer insert_application_and_token_for_refresh_token_expiry.sql for application info.
+    long refreshTokenExpiryInSecondsInDB = 72600;
+
+    @Test
+    public void testRefreshTokenExpiryAtRefreshGrant() throws Exception {
+
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(REFRESH_TOKEN);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-id");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        RefreshTokenValidationDataDO validationBean = new RefreshTokenValidationDataDO();
+        validationBean.setAccessTokenValidityInMillis(3600000L);
+        validationBean.setIssuedTime(Timestamp.from(Instant.now()));
+        validationBean.setAuthorizedUser(authenticatedUser);
+        validationBean.setGrantType(REFRESH_TOKEN);
+
+        tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationBean);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        RefreshGrantHandler refreshGrantHandler = spy(new RefreshGrantHandler());
+        refreshGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = refreshGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    @Test
+    public void testRefreshTokenExpiryAtAuthorizationCodeGrant() throws Exception {
+
+        PrivilegedCarbonContext.startTenantFlow();
+        PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("carbon.super");
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(AUTHORIZATION_CODE);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-abc");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        MockAuthzGrantHandler authorizationCodeGrantHandler = spy(new MockAuthzGrantHandler());
+        authorizationCodeGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = authorizationCodeGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    private static class MockAuthzGrantHandler extends AbstractAuthorizationGrantHandler {
+
+    }
+}
diff --git a/components/org.wso2.carbon.identity.oauth/src/test/resources/dbScripts/insert_application_and_token_for_refresh_token_expiry.sql b/components/org.wso2.carbon.identity.oauth/src/test/resources/dbScripts/insert_application_and_token_for_refresh_token_expiry.sql
new file mode 100644
index 0000000000..77b56a0645
--- /dev/null
+++ b/components/org.wso2.carbon.identity.oauth/src/test/resources/dbScripts/insert_application_and_token_for_refresh_token_expiry.sql
@@ -0,0 +1,32 @@
+INSERT INTO IDN_OAUTH_CONSUMER_APPS (CONSUMER_KEY, CONSUMER_SECRET, USERNAME, TENANT_ID, USER_DOMAIN, APP_NAME,
+            OAUTH_VERSION, CALLBACK_URL, GRANT_TYPES, APP_STATE, REFRESH_TOKEN_EXPIRE_TIME) VALUES
+            ('ca19a540f544777860e44e75f605d927', '87n9a540f544777860e44e75f605d435', 'user1', -1234, 'PRIMARY',
+            'myApp', 'OAuth-2.0', 'http://localhost:8080/redirect',
+            'refresh_token implicit password iwa:ntlm client_credentials authorization_code', 'ACTIVE',72600);
+
+INSERT INTO IDN_OAUTH_CONSUMER_APPS (CONSUMER_KEY, CONSUMER_SECRET, USERNAME, TENANT_ID, USER_DOMAIN, APP_NAME,
+            OAUTH_VERSION, CALLBACK_URL, GRANT_TYPES, APP_STATE, REFRESH_TOKEN_EXPIRE_TIME) VALUES
+            ('dabfba9390aa423f8b04332794d83614', '5dcae004fba64e3a8a7cbebfdf02fcde', 'user1', -1234, 'PRIMARY',
+            'myApp', 'OAuth-2.0', 'http://localhost:8080/redirect',
+            'refresh_token password iwa:ntlm client_credentials', 'ACTIVE',72600);
+
+INSERT INTO IDN_OAUTH2_ACCESS_TOKEN (TOKEN_ID, ACCESS_TOKEN, REFRESH_TOKEN, CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID,
+            USER_DOMAIN, USER_TYPE, GRANT_TYPE, TIME_CREATED, REFRESH_TOKEN_TIME_CREATED, VALIDITY_PERIOD,
+            REFRESH_TOKEN_VALIDITY_PERIOD, TOKEN_SCOPE_HASH, TOKEN_STATE, TOKEN_STATE_ID, SUBJECT_IDENTIFIER,
+            ACCESS_TOKEN_HASH, REFRESH_TOKEN_HASH, IDP_ID, AUTHORIZED_ORGANIZATION) VALUES
+            ('2sa9a678f890877856y66e75f605d456', 'd43e8da324a33bdc941b9b95cad6a6a2',
+            '2881c5a375d03dc0ba12787386451b29', 1, 'user1', -1234, 'PRIMARY', 'APPLICATION_USER', 'password', NOW(),
+            NOW(), 3600, 14400, '369db21a386ae433e65c0ff34d35708d', 'ACTIVE', 'NONE', 'user1', NULL, NULL, 1, 'NONE');
+
+INSERT INTO IDN_OAUTH2_ACCESS_TOKEN_SCOPE (TOKEN_ID, TOKEN_SCOPE, TENANT_ID) VALUES
+    ('2sa9a678f890877856y66e75f605d456', 'default', -1234);
+
+INSERT INTO IDN_OAUTH2_ACCESS_TOKEN (TOKEN_ID, ACCESS_TOKEN, REFRESH_TOKEN, CONSUMER_KEY_ID, AUTHZ_USER, TENANT_ID,
+            USER_DOMAIN, USER_TYPE, GRANT_TYPE, TIME_CREATED, REFRESH_TOKEN_TIME_CREATED, VALIDITY_PERIOD,
+            REFRESH_TOKEN_VALIDITY_PERIOD, TOKEN_SCOPE_HASH, TOKEN_STATE, TOKEN_STATE_ID, SUBJECT_IDENTIFIER,
+            ACCESS_TOKEN_HASH, REFRESH_TOKEN_HASH, IDP_ID, AUTHORIZED_ORGANIZATION) VALUES
+            ('a8f78c8420cb48ad91cbac72691d4597', 'eb354a2c07c9415583bab5f970a42ad5',
+            '12c05eeb22204114bc11f24c847a5cd3', 2, 'user1', -1234, 'PRIMARY', 'APPLICATION_USER', 'password', NOW(),
+            NOW(), 3600, 14400, '369db21a386ae433e65c0ff34d35708d', 'ACTIVE', 'NONE', 'user1', NULL, NULL, 1, 'NONE');
+
+INSERT INTO IDP (TENANT_ID, NAME, UUID) VALUES (1234, 'LOCAL', 5678);


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds unit tests to verify the refresh token expiry time behavior for both the refresh token and authorization code grants.  
- Adds testRefreshTokenExpiryAtRefreshGrant to validate the refresh token expiry at gesture of refresh token grant.  
- Adds testRefreshTokenExpiryAtAuthorizationCodeGrant to validate the expiry time for authorization code grants using a mock grant handler.

<details>
<summary>Files not reviewed (1)</summary>

* **components/org.wso2.carbon.identity.oauth/src/test/resources/dbScripts/insert_application_and_token_for_refresh_token_expiry.sql**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java:61**
* [nitpick] Consider renaming 'refreshTokenExpiryInSecondsInDB' (or declaring it as a static final constant) to emphasize that this value is a constant configuration parameter.
```
long refreshTokenExpiryInSecondsInDB = 72600;
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-19T14:58:44Z
user: login: malithie
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T15:01:54Z
user: login: indeewari
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T15:03:40Z

Review comments:
diff_hunk: @@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
+ *
+ *  WSO2 LLC. licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except
+ *  in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.wso2.carbon.identity.oauth2.token.handlers;
+
+import org.testng.annotations.Test;
+import org.wso2.carbon.CarbonConstants;
+import org.wso2.carbon.context.PrivilegedCarbonContext;
+import org.wso2.carbon.identity.action.execution.api.model.ActionType;
+import org.wso2.carbon.identity.action.execution.api.service.ActionExecutorService;
+import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithH2Database;
+import org.wso2.carbon.identity.common.testng.WithRealmService;
+import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
+import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO;
+import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
+import static org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler.PREV_ACCESS_TOKEN;
+
+/**
+ * This test class covers the refresh token expiry time behavior in the OAuthTokenReqMessageContext.
+ */
+@WithCarbonHome
+@WithH2Database(jndiName = "jdbc/WSO2IdentityDB",
+        files = {"dbScripts/identity.sql", "dbScripts/insert_application_and_token_for_refresh_token_expiry.sql",
+                "dbScripts/insert_consumer_app.sql", "dbScripts/insert_local_idp.sql"})
+@WithRealmService(injectToSingletons = {OAuthComponentServiceHolder.class})
+public class RefreshTokenExpiryTest {
+
+    // Refer insert_application_and_token_for_refresh_token_expiry.sql for application info.
+    long refreshTokenExpiryInSecondsInDB = 72600;
+
+    @Test
+    public void testRefreshTokenExpiryAtRefreshGrant() throws Exception {
+
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(REFRESH_TOKEN);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-id");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        RefreshTokenValidationDataDO validationBean = new RefreshTokenValidationDataDO();
+        validationBean.setAccessTokenValidityInMillis(3600000L);
+        validationBean.setIssuedTime(Timestamp.from(Instant.now()));
+        validationBean.setAuthorizedUser(authenticatedUser);
+        validationBean.setGrantType(REFRESH_TOKEN);
+
+        tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationBean);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        RefreshGrantHandler refreshGrantHandler = spy(new RefreshGrantHandler());
+        refreshGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = refreshGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    @Test
+    public void testRefreshTokenExpiryAtAuthorizationCodeGrant() throws Exception {
+
+        PrivilegedCarbonContext.startTenantFlow();
+        PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("carbon.super");
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(AUTHORIZATION_CODE);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-abc");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        MockAuthzGrantHandler authorizationCodeGrantHandler = spy(new MockAuthzGrantHandler());
+        authorizationCodeGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = authorizationCodeGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    private static class MockAuthzGrantHandler extends AbstractAuthorizationGrantHandler {
+
path: components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java
user: login: Copilot
body: Ensure that MockAuthzGrantHandler provides any necessary implementations of abstract methods if required for accurate test behavior, or explicitly document that the default behavior suits this test.
```suggestion

        @Override
        public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) {
            // No-op implementation for testing purposes.
            return true;
        }

        @Override
        public boolean issueRefreshToken() {
            // No-op implementation for testing purposes.
            return true;
        }

        @Override
        public boolean authorizeAccessDelegation(OAuthTokenReqMessageContext tokReqMsgCtx) {
            // No-op implementation for testing purposes.
            return true;
        }

        @Override
        public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) {
            // No-op implementation for testing purposes.
            return true;
        }
```
created_at: 2025-04-19T14:58:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com).
+ *
+ *  WSO2 LLC. licenses this file to you under the Apache License,
+ *  Version 2.0 (the "License"); you may not use this file except
+ *  in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.wso2.carbon.identity.oauth2.token.handlers;
+
+import org.testng.annotations.Test;
+import org.wso2.carbon.CarbonConstants;
+import org.wso2.carbon.context.PrivilegedCarbonContext;
+import org.wso2.carbon.identity.action.execution.api.model.ActionType;
+import org.wso2.carbon.identity.action.execution.api.service.ActionExecutorService;
+import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
+import org.wso2.carbon.identity.common.testng.WithCarbonHome;
+import org.wso2.carbon.identity.common.testng.WithH2Database;
+import org.wso2.carbon.identity.common.testng.WithRealmService;
+import org.wso2.carbon.identity.oauth.internal.OAuthComponentServiceHolder;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenReqDTO;
+import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
+import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO;
+import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
+import org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.AUTHORIZATION_CODE;
+import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
+import static org.wso2.carbon.identity.oauth2.token.handlers.grant.RefreshGrantHandler.PREV_ACCESS_TOKEN;
+
+/**
+ * This test class covers the refresh token expiry time behavior in the OAuthTokenReqMessageContext.
+ */
+@WithCarbonHome
+@WithH2Database(jndiName = "jdbc/WSO2IdentityDB",
+        files = {"dbScripts/identity.sql", "dbScripts/insert_application_and_token_for_refresh_token_expiry.sql",
+                "dbScripts/insert_consumer_app.sql", "dbScripts/insert_local_idp.sql"})
+@WithRealmService(injectToSingletons = {OAuthComponentServiceHolder.class})
+public class RefreshTokenExpiryTest {
+
+    // Refer insert_application_and_token_for_refresh_token_expiry.sql for application info.
+    long refreshTokenExpiryInSecondsInDB = 72600;
+
+    @Test
+    public void testRefreshTokenExpiryAtRefreshGrant() throws Exception {
+
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(REFRESH_TOKEN);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-id");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        RefreshTokenValidationDataDO validationBean = new RefreshTokenValidationDataDO();
+        validationBean.setAccessTokenValidityInMillis(3600000L);
+        validationBean.setIssuedTime(Timestamp.from(Instant.now()));
+        validationBean.setAuthorizedUser(authenticatedUser);
+        validationBean.setGrantType(REFRESH_TOKEN);
+
+        tokReqMsgCtx.addProperty(PREV_ACCESS_TOKEN, validationBean);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        RefreshGrantHandler refreshGrantHandler = spy(new RefreshGrantHandler());
+        refreshGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = refreshGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    @Test
+    public void testRefreshTokenExpiryAtAuthorizationCodeGrant() throws Exception {
+
+        PrivilegedCarbonContext.startTenantFlow();
+        PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("carbon.super");
+        CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = false;
+
+        OAuth2AccessTokenReqDTO tokenReq = mock(OAuth2AccessTokenReqDTO.class);
+        when(tokenReq.getClientId()).thenReturn("ca19a540f544777860e44e75f605d927");
+        when(tokenReq.getGrantType()).thenReturn(AUTHORIZATION_CODE);
+
+        AuthenticatedUser authenticatedUser = mock(AuthenticatedUser.class);
+        when(authenticatedUser.getTenantDomain()).thenReturn("carbon.super");
+        when(authenticatedUser.getUserId()).thenReturn("user-abc");
+
+        OAuthTokenReqMessageContext tokReqMsgCtx = spy(new OAuthTokenReqMessageContext(tokenReq));
+        String[] scopeArray = new String[]{"openid", "profile"};
+        tokReqMsgCtx.setScope(scopeArray);
+        tokReqMsgCtx.setAuthorizedUser(authenticatedUser);
+
+        ActionExecutorService actionExecutorService = mock(ActionExecutorService.class);
+        when(actionExecutorService.isExecutionEnabled(ActionType.PRE_ISSUE_ACCESS_TOKEN)).thenReturn(false);
+        OAuthComponentServiceHolder.getInstance().setActionExecutorService(actionExecutorService);
+
+        MockAuthzGrantHandler authorizationCodeGrantHandler = spy(new MockAuthzGrantHandler());
+        authorizationCodeGrantHandler.init();
+        OAuth2AccessTokenRespDTO refreshTokenDTO = authorizationCodeGrantHandler.issue(tokReqMsgCtx);
+        assertNotNull(refreshTokenDTO);
+        assertEquals(tokReqMsgCtx.getRefreshTokenvalidityPeriod(), refreshTokenExpiryInSecondsInDB * 1000);
+    }
+
+    private static class MockAuthzGrantHandler extends AbstractAuthorizationGrantHandler {
+
path: components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth2/token/handlers/RefreshTokenExpiryTest.java
user: login: indeewari
body: This is not applicable since the mock intention was to focus only on the required functionalities.
created_at: 2025-04-19T15:03:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: jenkins-is-staging
created_at: 2025-04-19T10:44:40Z
body: PR builder started 
Link: https://github.com/wso2/product-is/actions/runs/14548392040
author_association: NONE
user: login: jenkins-is-staging
created_at: 2025-04-19T12:28:16Z
body: PR builder completed 
Link: https://github.com/wso2/product-is/actions/runs/14548392040 
Status: **failure**
author_association: NONE
user: login: indeewari
created_at: 2025-04-19T14:39:51Z
body: > PR builder completed Link: https://github.com/wso2/product-is/actions/runs/14548392040 Status: **failure**

Known failure

```
 [INFO] Results:
 [INFO] 
 [ERROR] Failures: 
 [ERROR] org.wso2.identity.integration.test.rest.api.user.association.v1.UserMeSuccessTestBase.testGetFederatedAssociations(org.wso2.identity.integration.test.rest.api.user.association.v1.UserMeSuccessTestBase)
 [ERROR] Run 1: UserMeSuccessTestBase.testGetFederatedAssociations:204 1 expectation failed.
 JSON path size() doesn't match.
 Expected: is <2>
 Actual: <0>
 
 [ERROR] Run 2: UserMeSuccessTestBase.testGetFederatedAssociations:204 1 expectation failed.
 JSON path size() doesn't match.
 Expected: is <2>
 Actual: <0>
 
```
author_association: CONTRIBUTOR