From c48c89768dbd1b796313ded2fa832e383cb90478 Mon Sep 17 00:00:00 2001 From: Akshay Sonvane Date: Thu, 23 Jan 2025 14:23:29 -0800 Subject: [PATCH] W-17604924: add unit tests for package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth --- ...rizationCodeConnectionProviderWrapper.java | 7 +- .../AuthorizationCodeOAuthHandler.java | 22 +- .../ClientCredentialsOAuthHandler.java | 26 +- ...pdatingAuthorizationCodeStateTestCase.java | 32 ++- .../authcode/AuthorizationCodeConfigTest.java | 141 ++++++++++ ...tionCodeConnectionProviderWrapperTest.java | 108 ++++++++ .../AuthorizationCodeOAuthHandlerTest.java | 252 ++++++++++++++++++ .../authcode/OAuthCallbackConfigTest.java | 31 +++ .../ClientCredentialsConfigTest.java | 113 ++++++++ ...ialsConnectionProviderWrapperTestCase.java | 63 +++-- .../ClientCredentialsOAuthHandlerTest.java | 219 +++++++++++++++ .../UpdatingClientCredentialsStateTest.java | 112 ++++++++ 12 files changed, 1088 insertions(+), 38 deletions(-) create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConfigTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapperTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandlerTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/OAuthCallbackConfigTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConfigTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandlerTest.java create mode 100644 modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/UpdatingClientCredentialsStateTest.java diff --git a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapper.java b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapper.java index f94cc05c1fc8..a99b9c012c7a 100644 --- a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapper.java +++ b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapper.java @@ -57,8 +57,7 @@ public AuthorizationCodeConnectionProviderWrapper(ConnectionProvider delegate super(delegate, reconnectionConfig, callbackValues); this.oauthConfig = oauthConfig; this.oauthHandler = oauthHandler; - authCodeStateSetter = - getOAuthStateSetter(getDelegateForInjection(), AUTHORIZATION_CODE_STATE_INTERFACES, oauthConfig.getGrantType()); + authCodeStateSetter = resolveOauthStateSetter(oauthConfig); dance = Once.of(this::updateAuthState); this.forceInvalidateStatusRetrievalSupplier = forceInvalidateStatusRetrievalSupplier; } @@ -111,4 +110,8 @@ public void start() throws MuleException { dancer = oauthHandler.register(oauthConfig); super.start(); } + + protected FieldSetter resolveOauthStateSetter(AuthorizationCodeConfig oauthConfig) { + return getOAuthStateSetter(getDelegateForInjection(), AUTHORIZATION_CODE_STATE_INTERFACES, oauthConfig.getGrantType()); + } } diff --git a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandler.java b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandler.java index 2373e8ae25ea..87924fc8453d 100644 --- a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandler.java +++ b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandler.java @@ -51,10 +51,12 @@ import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.OAuthConfig; import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.OAuthHandler; import org.mule.runtime.module.extension.internal.store.LazyObjectStoreToMapAdapter; +import org.mule.runtime.oauth.api.OAuthService; import java.net.MalformedURLException; import java.net.URL; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; @@ -79,7 +81,7 @@ public class AuthorizationCodeOAuthHandler extends OAuthHandler httpService; + protected LazyValue httpService; private boolean forceInvalidateStatusRetrieval; @@ -162,11 +164,11 @@ private AuthorizationCodeOAuthDancer createDancer(AuthorizationCodeConfig config checkArgument(listeners != null, "listeners cannot be null"); OAuthAuthorizationCodeDancerBuilder dancerBuilder = - oauthService.get().authorizationCodeGrantTypeDancerBuilder(lockFactory, - new LazyObjectStoreToMapAdapter( - () -> objectStoreLocator - .apply(config)), - expressionEvaluator); + getOAuthService().get().authorizationCodeGrantTypeDancerBuilder(lockFactory, + new LazyObjectStoreToMapAdapter( + () -> objectStoreLocator + .apply(config)), + expressionEvaluator); final AuthorizationCodeGrantType grantType = config.getGrantType(); final OAuthCallbackConfig callbackConfig = config.getCallbackConfig(); @@ -313,4 +315,12 @@ public void initialise() throws InitialisationException { super.initialise(); httpService = new LazyLookup<>(HttpService.class, muleContext); } + + protected Map getDancers() { + return dancers; + } + + protected LazyValue getOAuthService() { + return oauthService; + } } diff --git a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandler.java b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandler.java index cb36d2bb4284..c0f9ed8cad8d 100644 --- a/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandler.java +++ b/modules/extensions-support/src/main/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandler.java @@ -17,19 +17,19 @@ import org.mule.oauth.client.api.builder.OAuthClientCredentialsDancerBuilder; import org.mule.oauth.client.api.listener.ClientCredentialsListener; import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; -import org.mule.runtime.api.config.ArtifactEncoding; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.exception.MuleRuntimeException; +import org.mule.runtime.api.util.LazyValue; import org.mule.runtime.core.api.util.func.CheckedFunction; import org.mule.runtime.extension.api.connectivity.oauth.ClientCredentialsGrantType; import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.OAuthHandler; import org.mule.runtime.module.extension.internal.store.LazyObjectStoreToMapAdapter; +import org.mule.runtime.oauth.api.OAuthService; import java.util.List; +import java.util.Map; import java.util.Objects; -import javax.inject.Inject; - /** * {@link OAuthHandler} implementation for the client credentials grant type * @@ -121,11 +121,11 @@ private ClientCredentialsOAuthDancer createDancer(ClientCredentialsConfig config checkArgument(listeners != null, "listeners cannot be null"); OAuthClientCredentialsDancerBuilder dancerBuilder = - oauthService.get().clientCredentialsGrantTypeDancerBuilder(lockFactory, - new LazyObjectStoreToMapAdapter( - () -> objectStoreLocator - .apply(config)), - expressionEvaluator); + getOAuthService().get().clientCredentialsGrantTypeDancerBuilder(lockFactory, + new LazyObjectStoreToMapAdapter( + () -> objectStoreLocator + .apply(config)), + expressionEvaluator); final ClientCredentialsGrantType grantType = config.getGrantType(); @@ -163,8 +163,16 @@ private ClientCredentialsOAuthDancer createDancer(ClientCredentialsConfig config return dancer; } - private Integer generateId(ClientCredentialsConfig config) { + protected Integer generateId(ClientCredentialsConfig config) { return Objects.hash(config.getOwnerConfigName(), config.getClientId(), config.getClientSecret(), config.getTokenUrl(), config.getScope(), config.getCustomQueryParameters(), config.getCustomHeaders()); } + + protected Map getDancers() { + return dancers; + } + + protected LazyValue getOAuthService() { + return oauthService; + } } diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/UpdatingAuthorizationCodeStateTestCase.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/UpdatingAuthorizationCodeStateTestCase.java index ff412fc811be..f2beedb398cc 100644 --- a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/UpdatingAuthorizationCodeStateTestCase.java +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/UpdatingAuthorizationCodeStateTestCase.java @@ -18,7 +18,6 @@ import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -52,7 +51,14 @@ public class UpdatingAuthorizationCodeStateTestCase extends AbstractMuleTestCase private static final String REFRESH_TOKEN = "myRefreshToken"; private static final String NEW_TOKEN = "newToken"; private static final String NEW_REFRESH_TOKEN = "newRefresh"; - private static final String RESOURCE_OWNER_ID = "id"; + private static final String RESOURCE_OWNER_ID = "exp"; + private static final String EXPIRES_IN = "expires_in"; + private static final String STATE = "state"; + private static final String CONSUMER_KEY = "key"; + private static final String CONSUMER_SECRET = "secret"; + private static final String EXTERNAL_CALLBACK_URL = "externalCallbackUrl"; + private static final String ACCESS_TOKEN_URL = "accesstokenurl"; + private static final String AUTHORIZATION_URL = "AuthorizationUrl"; @Rule public MockitoRule mockitorule = MockitoJUnit.rule(); @@ -68,23 +74,32 @@ public class UpdatingAuthorizationCodeStateTestCase extends AbstractMuleTestCase @Mock private ResourceOwnerOAuthContext refreshedContext; + @Mock + private OAuthCallbackConfig mockOAuthCallbackConfig; + @Before public void before() { oAuthConfig = new AuthorizationCodeConfig("configName", empty(), new CustomOAuthParameters(), emptyMap(), - new AuthorizationCodeGrantType("url", "url", "#[s]", "reg", "#[x]", "sd"), - mock(OAuthCallbackConfig.class), - "key", "secret", "url", "url", "scope", RESOURCE_OWNER_ID, null, null); + new AuthorizationCodeGrantType(ACCESS_TOKEN_URL, AUTHORIZATION_URL, "#[s]", "reg", + "#[x]", "sd"), + new OAuthCallbackConfig("", "", "", EXTERNAL_CALLBACK_URL), + CONSUMER_KEY, CONSUMER_SECRET, AUTHORIZATION_URL, ACCESS_TOKEN_URL, "scope", + RESOURCE_OWNER_ID, null, + null); when(initialContext.getAccessToken()).thenReturn(ACCESS_TOKEN); when(initialContext.getRefreshToken()).thenReturn(REFRESH_TOKEN); when(initialContext.getResourceOwnerId()).thenReturn(RESOURCE_OWNER_ID); + when(initialContext.getExpiresIn()).thenReturn(EXPIRES_IN); + when(initialContext.getState()).thenReturn(STATE); when(refreshedContext.getAccessToken()).thenReturn(NEW_TOKEN); when(refreshedContext.getRefreshToken()).thenReturn(NEW_REFRESH_TOKEN); when(refreshedContext.getResourceOwnerId()).thenReturn(RESOURCE_OWNER_ID); + when(refreshedContext.getExpiresIn()).thenReturn(EXPIRES_IN); } @Test @@ -101,6 +116,13 @@ public void onRefreshToken() { assertThat(state.getAccessToken(), equalTo(ACCESS_TOKEN)); assertThat(state.getRefreshToken().get(), equalTo(REFRESH_TOKEN)); + assertThat(state.getExternalCallbackUrl().get(), equalTo(EXTERNAL_CALLBACK_URL)); + assertThat(state.getState().get(), equalTo(STATE)); + assertThat(state.getExpiresIn().get(), equalTo(EXPIRES_IN)); + assertThat(state.getConsumerKey(), equalTo("key")); + assertThat(state.getConsumerSecret(), equalTo("secret")); + assertThat(state.getAuthorizationUrl(), equalTo(AUTHORIZATION_URL)); + assertThat(state.getAccessTokenUrl(), equalTo(ACCESS_TOKEN_URL)); AuthorizationCodeListener listener = listenerCaptor.getValue(); assertTokenRefreshed(newContext, state, listener); diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConfigTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConfigTest.java new file mode 100644 index 000000000000..792e6c0de13f --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConfigTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.authcode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.Mockito.mock; + +import org.mule.runtime.extension.api.connectivity.oauth.AuthorizationCodeGrantType; +import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.CustomOAuthParameters; +import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.OAuthObjectStoreConfig; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; + +public class AuthorizationCodeConfigTest { + + private static final String OWNER_CONFIG_NAME = "ownerConfig"; + private static final String CONSUMER_KEY = "consumerKey"; + private static final String CONSUMER_SECRET = "consumerSecret"; + private static final String AUTH_URL = "https://auth.url"; + private static final String TOKEN_URL = "https://token.url"; + private static final String SCOPE = "read write"; + private static final String BEFORE = "before"; + private static final String AFTER = "after"; + private static final String RESOURCE_OWNER_ID = "resourceOwnerId"; + + private AuthorizationCodeGrantType grantType; + private Optional storeConfig; + private CustomOAuthParameters customOAuthParameters; + private Map parameterExtractors; + private OAuthCallbackConfig callbackConfig; + + private AuthorizationCodeConfig config; + + @Before + public void setUp() { + grantType = mock(AuthorizationCodeGrantType.class); + storeConfig = Optional.of(mock(OAuthObjectStoreConfig.class)); + customOAuthParameters = mock(CustomOAuthParameters.class); + parameterExtractors = mock(Map.class); + callbackConfig = mock(OAuthCallbackConfig.class); + + config = new AuthorizationCodeConfig( + OWNER_CONFIG_NAME, + storeConfig, + customOAuthParameters, + parameterExtractors, + grantType, + callbackConfig, + CONSUMER_KEY, + CONSUMER_SECRET, + AUTH_URL, + TOKEN_URL, + SCOPE, + RESOURCE_OWNER_ID, + BEFORE, + AFTER); + } + + @Test + public void testGetConsumerKey() { + assertThat(config.getConsumerKey(), is(CONSUMER_KEY)); + } + + @Test + public void testGetConsumerSecret() { + assertThat(config.getConsumerSecret(), is(CONSUMER_SECRET)); + } + + @Test + public void testGetAccessTokenUrl() { + assertThat(config.getAccessTokenUrl(), is(TOKEN_URL)); + } + + @Test + public void testGetAuthorizationUrl() { + assertThat(config.getAuthorizationUrl(), is(AUTH_URL)); + } + + @Test + public void testGetScope() { + assertThat(config.getScope().isPresent(), is(true)); + assertThat(config.getScope().get(), is(SCOPE)); + } + + @Test + public void testGetScopeWhenNull() { + AuthorizationCodeConfig configWithNullScope = new AuthorizationCodeConfig( + OWNER_CONFIG_NAME, + storeConfig, + customOAuthParameters, + parameterExtractors, + grantType, + callbackConfig, + CONSUMER_KEY, + CONSUMER_SECRET, + AUTH_URL, + TOKEN_URL, + null, + RESOURCE_OWNER_ID, + BEFORE, + AFTER); + assertThat(configWithNullScope.getScope().isPresent(), is(false)); + } + + @Test + public void testGetGrantType() { + assertThat(config.getGrantType(), is(grantType)); + } + + @Test + public void testGetResourceOwnerId() { + assertThat(config.getResourceOwnerId(), is(RESOURCE_OWNER_ID)); + } + + @Test + public void testGetCallbackConfig() { + assertThat(config.getCallbackConfig(), is(callbackConfig)); + } + + @Test + public void testGetBefore() { + assertThat(config.getBefore().isPresent(), is(true)); + assertThat(config.getBefore().get(), is(BEFORE)); + } + + @Test + public void testGetAfter() { + assertThat(config.getAfter().isPresent(), is(true)); + assertThat(config.getAfter().get(), is(AFTER)); + } +} diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapperTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapperTest.java new file mode 100644 index 000000000000..9392bee2674e --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeConnectionProviderWrapperTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.authcode; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.mule.oauth.client.api.AuthorizationCodeOAuthDancer; +import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; +import org.mule.runtime.api.connection.ConnectionException; +import org.mule.runtime.api.connection.ConnectionProvider; +import org.mule.runtime.api.exception.MuleException; +import org.mule.runtime.core.api.retry.ReconnectionConfig; +import org.mule.runtime.extension.api.connectivity.oauth.AuthorizationCodeGrantType; +import org.mule.runtime.module.extension.internal.util.FieldSetter; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.junit.Test; + +public class AuthorizationCodeConnectionProviderWrapperTest { + + private AuthorizationCodeOAuthHandler oauthHandler; + private AuthorizationCodeGrantType type; + private AuthorizationCodeConnectionProviderWrapper wrapper; + + @Test + public void listenerIsRegistered() throws MuleException { + setupCommonMocks(); + AuthorizationCodeOAuthDancer dancer = mock(AuthorizationCodeOAuthDancer.class); + when(oauthHandler.register(any())).thenReturn(dancer); + + wrapper.start(); + wrapper.connect(); + verify(dancer, times(1)).addListener(any(), any()); + } + + @Test + public void refreshTokenCallsOauthHandler() throws MuleException { + setupCommonMocks(); + wrapper.refreshToken("id1"); + verify(oauthHandler, times(1)).refreshToken(any(), any()); + } + + @Test + public void invalidateCallsOauthHandler() throws MuleException { + setupCommonMocks(); + wrapper.invalidate("id1"); + verify(oauthHandler, times(1)).invalidate(any(), any()); + } + + @Test + public void getOAuthGrantType() throws MuleException { + setupCommonMocks(); + assertThat(wrapper.getGrantType(), is(type)); + } + + private void setupCommonMocks() throws ConnectionException { + ConnectionProvider delegate = mock(ConnectionProvider.class); + when(delegate.connect()).thenReturn(new Object()); + + AuthorizationCodeConfig oauthConfig = mock(AuthorizationCodeConfig.class); + this.type = new AuthorizationCodeGrantType("http://accessToken", + "http://auth", + "#[accessToken]", + ".*", + "#[refreshToken]", + null); + when(oauthConfig.getGrantType()).thenReturn(type); + when(oauthConfig.getCallbackConfig()).thenReturn(mock(OAuthCallbackConfig.class)); + Map callbackValues = new HashMap<>(); + this.oauthHandler = mock(AuthorizationCodeOAuthHandler.class); + when(oauthHandler.getOAuthContext(any())).thenReturn(Optional.ofNullable(mock(ResourceOwnerOAuthContext.class))); + ReconnectionConfig reconnectionConfig = mock(ReconnectionConfig.class); + this.wrapper = + new AuthorizationCodeConnectionProviderWrapperTest.TestAuthorizationCodeConnectionProviderWrapper(delegate, oauthConfig, + callbackValues, + oauthHandler, + reconnectionConfig); + } + + private class TestAuthorizationCodeConnectionProviderWrapper extends AuthorizationCodeConnectionProviderWrapper { + + public TestAuthorizationCodeConnectionProviderWrapper(ConnectionProvider delegate, AuthorizationCodeConfig oauthConfig, + Map callbackValues, + AuthorizationCodeOAuthHandler oauthHandler, + ReconnectionConfig reconnectionConfig) { + super(delegate, oauthConfig, callbackValues, oauthHandler, reconnectionConfig, () -> false); + } + + @Override + protected FieldSetter resolveOauthStateSetter(AuthorizationCodeConfig oauthConfig) { + return mock(FieldSetter.class); + } + } +} diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandlerTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandlerTest.java new file mode 100644 index 000000000000..e071131456f4 --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/AuthorizationCodeOAuthHandlerTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.authcode; + +import static org.mule.runtime.extension.api.security.CredentialsPlacement.BASIC_AUTH_HEADER; + +import static java.nio.charset.Charset.defaultCharset; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.mule.oauth.client.api.AuthorizationCodeOAuthDancer; +import org.mule.oauth.client.api.listener.AuthorizationCodeListener; +import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; +import org.mule.runtime.api.config.ArtifactEncoding; +import org.mule.runtime.api.exception.MuleRuntimeException; +import org.mule.runtime.api.util.LazyValue; +import org.mule.runtime.api.util.MultiMap; +import org.mule.runtime.extension.api.connectivity.oauth.AuthorizationCodeGrantType; +import org.mule.runtime.http.api.HttpService; +import org.mule.runtime.http.api.server.HttpServer; +import org.mule.runtime.http.api.server.HttpServerFactory; +import org.mule.runtime.http.api.server.ServerNotFoundException; +import org.mule.runtime.oauth.api.OAuthService; +import org.mule.runtime.oauth.api.builder.OAuthAuthorizationCodeDancerBuilder; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class AuthorizationCodeOAuthHandlerTest { + + @Mock + private AuthorizationCodeConfig mockConfig; + + @Mock + private AuthorizationCodeOAuthDancer mockDancer; + + @Mock + private ResourceOwnerOAuthContext mockContext; + + @Mock + private OAuthAuthorizationCodeDancerBuilder mockDancerBuilder; + + @Mock + private AuthorizationCodeGrantType mockGrantType; + + @Mock + private LazyValue mockOAuthService; + + @Mock + private OAuthService mockOAuthServiceInstance; + + @Mock + private ArtifactEncoding artifactEncoding; + + @Mock + private LazyValue mockHttpServiceInstance; + + @Mock + private HttpService mockHttpService; + + @Mock + private HttpServerFactory mockHttpServerFactory; + + @Mock + private HttpServer mockHttpServer; + + @Mock + private OAuthCallbackConfig mockOAuthCallbackConfig; + + @Mock + private ResourceOwnerOAuthContext mockResourceOwnerOAuthContext; + + @InjectMocks + private AuthorizationCodeOAuthHandler handler = spy(new AuthorizationCodeOAuthHandler()); + + + @Before + public void setUp() throws ServerNotFoundException { + MockitoAnnotations.initMocks(this); + + when(artifactEncoding.getDefaultEncoding()).thenReturn(defaultCharset()); + + when(mockConfig.getOwnerConfigName()).thenReturn("owner-config"); + when(mockConfig.getAccessTokenUrl()).thenReturn("url"); + when(mockConfig.getConsumerKey()).thenReturn("key"); + when(mockConfig.getConsumerSecret()).thenReturn("secret"); + when(mockConfig.getGrantType()).thenReturn(mockGrantType); + when(mockConfig.getCallbackConfig()).thenReturn(mockOAuthCallbackConfig); + when(mockConfig.getAuthorizationUrl()).thenReturn("auth-url"); + MultiMap testMap = new MultiMap<>(); + when(mockConfig.getCustomQueryParameters()).thenReturn(testMap); + when(mockConfig.getCustomHeaders()).thenReturn(testMap); + when(mockConfig.getCustomBodyParameters()).thenReturn(testMap); + Map parameterExtractorsMap = new HashMap<>(); + when(mockConfig.getParameterExtractors()).thenReturn(parameterExtractorsMap); + when(mockConfig.getResourceOwnerId()).thenReturn("resource-id"); + when(mockConfig.getScope()).thenReturn(Optional.of("scope1")); + + when(handler.getOAuthService()).thenReturn(mockOAuthService); + when(mockOAuthService.get()).thenReturn(mockOAuthServiceInstance); + when(mockOAuthServiceInstance.authorizationCodeGrantTypeDancerBuilder(any(), any(), any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.name(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.encoding(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.clientCredentials(anyString(), anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.tokenUrl(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.responseExpiresInExpr(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.responseRefreshTokenExpr(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.responseAccessTokenExpr(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.resourceOwnerIdTransformer(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.withClientCredentialsIn(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.externalCallbackUrl(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.authorizationUrl(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.localCallback(any(), anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.localAuthorizationUrlPath(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.localAuthorizationUrlResourceOwnerId(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.state(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customParameters(anyMap())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customHeaders(anyMap())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customBodyParameters(anyMap())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customParametersExtractorsExprs(null)).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.includeRedirectUriInRefreshTokenRequest(anyBoolean())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.build()).thenReturn(mockDancer); + + when(mockGrantType.getExpirationRegex()).thenReturn("expires_in"); + when(mockGrantType.getRefreshTokenExpr()).thenReturn("expires_in"); + when(mockGrantType.getAccessTokenExpr()).thenReturn("expires_in"); + when(mockGrantType.getCredentialsPlacement()).thenReturn(BASIC_AUTH_HEADER); + when(mockOAuthCallbackConfig.getListenerConfig()).thenReturn("listener-config"); + when(mockOAuthCallbackConfig.getExternalCallbackUrl()).thenReturn(Optional.of("callback/url")); + when(mockOAuthCallbackConfig.getCallbackPath()).thenReturn("callback-path"); + when(mockOAuthCallbackConfig.getLocalAuthorizePath()).thenReturn("local-auth-path"); + + handler.getDancers().put("configName", mockDancer); + handler.httpService = mockHttpServiceInstance; + when(mockHttpServiceInstance.get()).thenReturn(mockHttpService); + when(mockHttpService.getServerFactory()).thenReturn(mockHttpServerFactory); + when(mockHttpServerFactory.lookup(anyString())).thenReturn(mockHttpServer); + } + + @Test + public void testRegister() throws Exception { + AuthorizationCodeListener listener = mock(AuthorizationCodeListener.class); + List listeners = Collections.singletonList(listener); + + AuthorizationCodeOAuthDancer result = handler.register(mockConfig, listeners); + + assertThat(result, notNullValue()); + verify(handler, times(1)).register(mockConfig, listeners); + verify(mockDancerBuilder, times(1)).build(); + } + + @Test(expected = MuleRuntimeException.class) + public void testRegisterFailure() throws Exception { + AuthorizationCodeListener listener = mock(AuthorizationCodeListener.class); + List listeners = Collections.singletonList(listener); + + doThrow(new ServerNotFoundException("server not found")) + .when(mockHttpServerFactory).lookup(anyString()); + + handler.register(mockConfig, listeners); + } + + @Test + public void testInvalidate() { + handler.invalidate("configName", ""); + + verify(mockDancer, times(1)).invalidateContext(anyString(), anyBoolean()); + } + + @Test + public void testInvalidateUnregistered() { + handler.getDancers().clear(); + + handler.invalidate("configName", ""); + + // Ensure no exceptions are thrown and no interaction with dancer occurs + verify(mockDancer, times(0)).invalidateContext(anyString(), anyBoolean()); + } + + @Test(expected = IllegalArgumentException.class) + public void testRegisterWithNullListeners() throws Exception { + handler.register(mockConfig, null); + } + + @Test + public void testRegisterOverloaded() { + AuthorizationCodeOAuthDancer dancer = handler.register(mockConfig); + assertThat(dancer, notNullValue()); + } + + @Test + public void testRefreshToken() { + doReturn(mock(CompletableFuture.class)).when(mockDancer).refreshToken("ownerID"); + + handler.refreshToken("configName", "ownerID"); + + verify(mockDancer, times(1)).refreshToken("ownerID"); + } + + @Test(expected = MuleRuntimeException.class) + public void testRefreshTokenFailure() throws Exception { + CompletableFuture future = mock(CompletableFuture.class); + when(mockDancer.refreshToken("ownerID")).thenReturn(future); + + doThrow(new ExecutionException("Token refresh failed", new Throwable())) + .when(future).get(); + + handler.refreshToken("configName", "ownerID"); + } + + @Test + public void testGetOAuthContextSuccess() throws Exception { + when(mockDancer.getContextForResourceOwner(anyString())).thenReturn(mockResourceOwnerOAuthContext); + when(mockResourceOwnerOAuthContext.getAccessToken()).thenReturn("access_token"); + when(mockContext.getAccessToken()).thenReturn("access-token"); + + handler.getDancers().put("owner-config", mockDancer); + + Optional context = handler.getOAuthContext(mockConfig); + + assertThat(context, notNullValue()); + } +} + diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/OAuthCallbackConfigTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/OAuthCallbackConfigTest.java new file mode 100644 index 000000000000..1c1bfed665dd --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/authcode/OAuthCallbackConfigTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.authcode; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class OAuthCallbackConfigTest { + + private OAuthCallbackConfig oAuthConfig; + + private static final String LISTENER_CONFIG = "listenerConfig"; + private static final String CALLBACK_PATH = "callbackPath"; + private static final String LOCAL_AUTHORIZE_PATH = "localAuthorizePath"; + private static final String EXTERNAL_CALLBACK_URL = "externalCallbackUrl"; + + @Test + public void testGetConsumerKey() { + oAuthConfig = new OAuthCallbackConfig(LISTENER_CONFIG, CALLBACK_PATH, LOCAL_AUTHORIZE_PATH, EXTERNAL_CALLBACK_URL); + + assertEquals(oAuthConfig.getListenerConfig(), LISTENER_CONFIG); + assertEquals(oAuthConfig.getCallbackPath(), CALLBACK_PATH); + assertEquals(oAuthConfig.getLocalAuthorizePath(), LOCAL_AUTHORIZE_PATH); + assertEquals(oAuthConfig.getExternalCallbackUrl().get(), EXTERNAL_CALLBACK_URL); + } +} diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConfigTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConfigTest.java new file mode 100644 index 000000000000..0086038e258e --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConfigTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.clientcredentials; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.mule.runtime.extension.api.connectivity.oauth.ClientCredentialsGrantType; +import org.mule.runtime.extension.api.security.CredentialsPlacement; +import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.CustomOAuthParameters; +import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.OAuthObjectStoreConfig; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; + +public class ClientCredentialsConfigTest { + + private static final String OWNER_CONFIG_NAME = "ownerConfig"; + private static final String CLIENT_ID = "testClientId"; + private static final String CLIENT_SECRET = "testClientSecret"; + private static final String TOKEN_URL = "https://token.url"; + private static final String SCOPE = "read write"; + private static final CredentialsPlacement CREDENTIALS_PLACEMENT = CredentialsPlacement.BASIC_AUTH_HEADER; + + private ClientCredentialsGrantType grantType; + private Optional storeConfig; + private CustomOAuthParameters customOAuthParameters; + private Map parameterExtractors; + + private ClientCredentialsConfig config; + + @Before + public void setUp() { + grantType = mock(ClientCredentialsGrantType.class); + storeConfig = Optional.of(mock(OAuthObjectStoreConfig.class)); + customOAuthParameters = mock(CustomOAuthParameters.class); + parameterExtractors = mock(Map.class); + + config = new ClientCredentialsConfig( + OWNER_CONFIG_NAME, + storeConfig, + customOAuthParameters, + parameterExtractors, + CLIENT_ID, + CLIENT_SECRET, + TOKEN_URL, + SCOPE, + CREDENTIALS_PLACEMENT, + grantType); + } + + @Test + public void testGetClientId() { + assertThat(config.getClientId(), is(CLIENT_ID)); + } + + @Test + public void testGetClientSecret() { + assertThat(config.getClientSecret(), is(CLIENT_SECRET)); + } + + @Test + public void testGetTokenUrl() { + assertThat(config.getTokenUrl(), is(TOKEN_URL)); + } + + @Test + public void testGetCredentialsPlacement() { + assertThat(config.getCredentialsPlacement(), is(CREDENTIALS_PLACEMENT)); + } + + @Test + public void testGetScope() { + assertThat(config.getScope().isPresent(), is(true)); + assertThat(config.getScope().get(), is(SCOPE)); + } + + @Test + public void testGetScopeWhenNull() { + ClientCredentialsConfig configWithNullScope = new ClientCredentialsConfig( + OWNER_CONFIG_NAME, + storeConfig, + customOAuthParameters, + parameterExtractors, + CLIENT_ID, + CLIENT_SECRET, + TOKEN_URL, + null, + CREDENTIALS_PLACEMENT, + grantType); + assertThat(configWithNullScope.getScope().isPresent(), is(false)); + } + + @Test + public void testGetConfigIdentifier() { + String expectedIdentifier = OWNER_CONFIG_NAME + "//" + CLIENT_ID + "//" + CLIENT_SECRET + "//" + TOKEN_URL + "//" + SCOPE; + assertThat(config.getConfigIdentifier(), is(expectedIdentifier)); + } + + @Test + public void testGetGrantType() { + assertThat(config.getGrantType(), is(grantType)); + } +} diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConnectionProviderWrapperTestCase.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConnectionProviderWrapperTestCase.java index 76633a3fed04..0f1f0ca9f4d1 100644 --- a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConnectionProviderWrapperTestCase.java +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsConnectionProviderWrapperTestCase.java @@ -9,6 +9,8 @@ import static org.mule.runtime.extension.api.security.CredentialsPlacement.BODY; import static org.mule.test.allure.AllureConstants.OauthFeature.SDK_OAUTH_SUPPORT; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -17,6 +19,7 @@ import org.mule.oauth.client.api.ClientCredentialsOAuthDancer; import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; +import org.mule.runtime.api.connection.ConnectionException; import org.mule.runtime.api.connection.ConnectionProvider; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.core.api.retry.ReconnectionConfig; @@ -32,37 +35,65 @@ import io.qameta.allure.Feature; import io.qameta.allure.Issue; -@Issue("W-14391247") @Feature(SDK_OAUTH_SUPPORT) public class ClientCredentialsConnectionProviderWrapperTestCase { + private ClientCredentialsOAuthHandler oauthHandler; + private ClientCredentialsGrantType type; + private ClientCredentialsConnectionProviderWrapper wrapper; + @Test + @Issue("W-14391247") public void listenerIsUnRegisteredOnStop() throws MuleException { + setupCommonMocks(); + ClientCredentialsOAuthDancer dancer = mock(ClientCredentialsOAuthDancer.class); + when(oauthHandler.register(any())).thenReturn(dancer); + + wrapper.start(); + wrapper.connect(); + verify(dancer, times(1)).addListener(any()); + wrapper.stop(); + verify(dancer, times(1)).removeListener(any()); + } + + @Test + public void refreshTokenCallsOauthHandler() throws MuleException { + setupCommonMocks(); + wrapper.refreshToken("id1"); + verify(oauthHandler, times(1)).refreshToken(any()); + } + + @Test + public void invalidateCallsOauthHandler() throws MuleException { + setupCommonMocks(); + wrapper.invalidate("id1"); + verify(oauthHandler, times(1)).invalidate(any()); + } + + @Test + public void getOAuthGrantType() throws MuleException { + setupCommonMocks(); + assertThat(wrapper.getGrantType(), is(type)); + } + + private void setupCommonMocks() throws ConnectionException { ConnectionProvider delegate = mock(ConnectionProvider.class); when(delegate.connect()).thenReturn(new Object()); ClientCredentialsConfig oauthConfig = mock(ClientCredentialsConfig.class); - ClientCredentialsGrantType type = new ClientCredentialsGrantType("http://accessToken", - "#[accessToken]", - ".*", - null, - BODY); + this.type = new ClientCredentialsGrantType("http://accessToken", + "#[accessToken]", + ".*", + null, + BODY); when(oauthConfig.getGrantType()).thenReturn(type); Map callbackValues = new HashMap<>(); - ClientCredentialsOAuthHandler oauthHandler = mock(ClientCredentialsOAuthHandler.class); + this.oauthHandler = mock(ClientCredentialsOAuthHandler.class); when(oauthHandler.getOAuthContext(any())).thenReturn(mock(ResourceOwnerOAuthContext.class)); ReconnectionConfig reconnectionConfig = mock(ReconnectionConfig.class); - ClientCredentialsConnectionProviderWrapper wrapper = + this.wrapper = new TestClientCredentialsConnectionProviderWrapper(delegate, oauthConfig, callbackValues, oauthHandler, reconnectionConfig); - ClientCredentialsOAuthDancer dancer = mock(ClientCredentialsOAuthDancer.class); - when(oauthHandler.register(any())).thenReturn(dancer); - - wrapper.start(); - wrapper.connect(); - verify(dancer, times(1)).addListener(any()); - wrapper.stop(); - verify(dancer, times(1)).removeListener(any()); } private class TestClientCredentialsConnectionProviderWrapper extends ClientCredentialsConnectionProviderWrapper { diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandlerTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandlerTest.java new file mode 100644 index 000000000000..b451a7d969cf --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/ClientCredentialsOAuthHandlerTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.clientcredentials; + +import static org.mule.runtime.extension.api.security.CredentialsPlacement.BASIC_AUTH_HEADER; + +import static java.nio.charset.Charset.defaultCharset; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mule.oauth.client.api.ClientCredentialsOAuthDancer; +import org.mule.oauth.client.api.listener.ClientCredentialsListener; +import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; +import org.mule.runtime.api.config.ArtifactEncoding; +import org.mule.runtime.api.exception.MuleRuntimeException; +import org.mule.runtime.api.util.LazyValue; +import org.mule.runtime.api.util.MultiMap; +import org.mule.runtime.extension.api.connectivity.oauth.ClientCredentialsGrantType; +import org.mule.runtime.oauth.api.OAuthService; +import org.mule.runtime.oauth.api.builder.OAuthClientCredentialsDancerBuilder; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class ClientCredentialsOAuthHandlerTest { + + @Mock + private ClientCredentialsConfig mockConfig; + + @Mock + private ClientCredentialsOAuthDancer mockDancer; + + @Mock + private ResourceOwnerOAuthContext mockContext; + + @Mock + private OAuthClientCredentialsDancerBuilder mockDancerBuilder; + + @Mock + private ClientCredentialsGrantType mockGrantType; + + @Mock + private LazyValue mockOAuthService; + + @Mock + private OAuthService mockOAuthServiceInstance; + + @Mock + private ArtifactEncoding artifactEncoding; + + @InjectMocks + private ClientCredentialsOAuthHandler handler = spy(new ClientCredentialsOAuthHandler()); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(artifactEncoding.getDefaultEncoding()).thenReturn(defaultCharset()); + + when(mockConfig.getConfigIdentifier()).thenReturn("config-id"); + when(mockConfig.getOwnerConfigName()).thenReturn("owner-config"); + when(mockConfig.getTokenUrl()).thenReturn("url"); + when(mockDancer.getContext()).thenReturn(mockContext); + + when(mockConfig.getGrantType()).thenReturn(mockGrantType); + when(mockConfig.getClientId()).thenReturn("client"); + when(mockConfig.getClientSecret()).thenReturn("secret"); + when(mockConfig.getCustomHeaders()).thenReturn(new MultiMap<>()); + when(mockConfig.getCustomBodyParameters()).thenReturn(new MultiMap<>()); + when(mockGrantType.getExpirationRegex()).thenReturn("expires_in"); + when(mockGrantType.getAccessTokenExpr()).thenReturn("access_token"); + when(mockGrantType.getCredentialsPlacement()).thenReturn(BASIC_AUTH_HEADER); + when(mockGrantType.getDefaultScopes()).thenReturn(Optional.of("scope")); + when(handler.getOAuthService()).thenReturn(mockOAuthService); + when(mockOAuthService.get()).thenReturn(mockOAuthServiceInstance); + when(mockOAuthServiceInstance.clientCredentialsGrantTypeDancerBuilder( + any(), any(), any())) + .thenReturn(mockDancerBuilder); + + when(mockDancerBuilder.name(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.encoding(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.clientCredentials(anyString(), anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.tokenUrl(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.responseExpiresInExpr(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.responseAccessTokenExpr(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.withClientCredentialsIn(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.scopes(anyString())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customParameters(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customHeaders(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customBodyParameters(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.customParametersExtractorsExprs(any())).thenReturn(mockDancerBuilder); + when(mockDancerBuilder.build()).thenReturn(mockDancer); + + handler.getDancers().put("config-id", mockDancer); + } + + @Test + public void testRegister() throws Exception { + handler.getDancers().remove("config-id"); + ClientCredentialsListener listener = mock(ClientCredentialsListener.class); + List listeners = Collections.singletonList(listener); + + ClientCredentialsOAuthDancer result = handler.register(mockConfig, listeners); + + assertThat(result, notNullValue()); + verify(handler, times(1)).register(mockConfig, listeners); + verify(mockDancerBuilder, times(1)).build(); + } + + @Test + public void testRefreshToken() { + doReturn(mock(CompletableFuture.class)).when(mockDancer).refreshToken(); + + handler.refreshToken(mockConfig); + + verify(mockDancer, times(1)).refreshToken(); + } + + @Test(expected = MuleRuntimeException.class) + public void testRefreshTokenFailure() throws Exception { + CompletableFuture future = mock(CompletableFuture.class); + when(mockDancer.refreshToken()).thenReturn(future); + + doThrow(new ExecutionException("Token refresh failed", new Throwable())) + .when(future).get(); + + handler.refreshToken(mockConfig); + } + + @Test + public void testGetOAuthContext() throws Exception { + CompletableFuture future = mock(CompletableFuture.class); + when(mockDancer.accessToken()).thenReturn(future); + when(future.get()).thenReturn(any()); + + ResourceOwnerOAuthContext context = handler.getOAuthContext(mockConfig); + + assertThat(context, notNullValue()); + verify(mockDancer, times(2)).getContext(); + } + + @Test + public void testGetOAuthContextSuccess() throws Exception { + when(mockContext.getAccessToken()).thenReturn("access-token"); + + ResourceOwnerOAuthContext context = handler.getOAuthContext(mockConfig); + + assertThat(context, notNullValue()); + verify(mockDancer, times(1)).getContext(); + } + + @Test(expected = IllegalStateException.class) + public void testGetOAuthContextUnregistered() { + handler.getDancers().clear(); + + handler.getOAuthContext(mockConfig); + } + + @Test(expected = MuleRuntimeException.class) + public void testGetOAuthContextFailure() throws Exception { + CompletableFuture future = mock(CompletableFuture.class); + when(mockDancer.accessToken()).thenReturn(future); + + doThrow(new ExecutionException("Token refresh failed", new Throwable())) + .when(future).get(); + + handler.getOAuthContext(mockConfig); + } + + @Test + public void testInvalidate() { + handler.invalidate(mockConfig); + + verify(mockDancer, times(1)).invalidateContext(); + } + + @Test + public void testInvalidateUnregistered() { + handler.getDancers().clear(); + + handler.invalidate(mockConfig); + + // Ensure no exceptions are thrown and no interaction with dancer occurs + verify(mockDancer, times(0)).invalidateContext(); + } + + @Test + public void testRegisterOverloaded() { + ClientCredentialsOAuthDancer clientCredentialsOAuthDancer = handler.register(mockConfig); + assertThat(clientCredentialsOAuthDancer, notNullValue()); + } + + @Test + public void testGenerateId() { + assertThat(handler.generateId(mockConfig), notNullValue()); + } +} diff --git a/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/UpdatingClientCredentialsStateTest.java b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/UpdatingClientCredentialsStateTest.java new file mode 100644 index 000000000000..3ad6e095d77b --- /dev/null +++ b/modules/extensions-support/src/test/java/org/mule/runtime/module/extension/internal/runtime/connectivity/oauth/clientcredentials/UpdatingClientCredentialsStateTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Salesforce, Inc. All rights reserved. + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.clientcredentials; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import org.mule.oauth.client.api.ClientCredentialsOAuthDancer; +import org.mule.oauth.client.api.listener.ClientCredentialsListener; +import org.mule.oauth.client.api.state.ResourceOwnerOAuthContext; +import org.mule.runtime.module.extension.internal.runtime.connectivity.oauth.exception.TokenInvalidatedException; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class UpdatingClientCredentialsStateTest { + + private ClientCredentialsOAuthDancer dancer; + private ResourceOwnerOAuthContext initialContext; + private Consumer onUpdate; + private UpdatingClientCredentialsState updatingClientCredentialsState; + + @Before + public void setUp() { + dancer = mock(ClientCredentialsOAuthDancer.class); + initialContext = mock(ResourceOwnerOAuthContext.class); + onUpdate = mock(Consumer.class); + + when(initialContext.getAccessToken()).thenReturn("initialAccessToken"); + when(initialContext.getExpiresIn()).thenReturn("3600"); + + updatingClientCredentialsState = new UpdatingClientCredentialsState(dancer, initialContext, onUpdate); + } + + @Test + public void testGetAccessTokenWhenNotInvalidated() { + String accessToken = updatingClientCredentialsState.getAccessToken(); + + assertEquals("initialAccessToken", accessToken); + } + + @Test + public void testGetAccessTokenWhenInvalidatedAndTokenRefreshed() throws Exception { + CompletableFuture future = mock(CompletableFuture.class); + when(dancer.accessToken()).thenReturn(future); + + // Simulate token invalidation + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ClientCredentialsListener.class); + verify(dancer).addListener(listenerCaptor.capture()); + ClientCredentialsListener listener = listenerCaptor.getValue(); + + listener.onTokenInvalidated(); + + when(dancer.getContext()).thenReturn(initialContext); + when(initialContext.getAccessToken()).thenReturn("newAccessToken"); + + String accessToken = updatingClientCredentialsState.getAccessToken(); + assertEquals("newAccessToken", accessToken); + } + + @Test(expected = TokenInvalidatedException.class) + public void testGetAccessTokenWhenInvalidatedAndTokenRefreshFails() throws Exception { + when(dancer.accessToken()).thenThrow(new RuntimeException("Refresh failed")); + + // Simulate token invalidation + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ClientCredentialsListener.class); + verify(dancer).addListener(listenerCaptor.capture()); + ClientCredentialsListener listener = listenerCaptor.getValue(); + + listener.onTokenInvalidated(); + + updatingClientCredentialsState.getAccessToken(); + } + + @Test + public void testOnTokenRefreshed() { + ResourceOwnerOAuthContext updatedContext = mock(ResourceOwnerOAuthContext.class); + when(updatedContext.getAccessToken()).thenReturn("updatedAccessToken"); + when(updatedContext.getExpiresIn()).thenReturn("7200"); + + // Simulate token refresh + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ClientCredentialsListener.class); + verify(dancer).addListener(listenerCaptor.capture()); + ClientCredentialsListener listener = listenerCaptor.getValue(); + + listener.onTokenRefreshed(updatedContext); + + verify(onUpdate).accept(updatedContext); + assertEquals("updatedAccessToken", updatingClientCredentialsState.getAccessToken()); + } + + @Test + public void testDeregisterListener() { + updatingClientCredentialsState.deregisterListener(); + + verify(dancer).removeListener(any(ClientCredentialsListener.class)); + } + + @Test + public void testGetExpiresIn() { + assertNotNull(updatingClientCredentialsState.getExpiresIn()); + } +} +