diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index cd0af114..e25b4781 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -60,7 +60,7 @@ org.slf4j slf4j-simple - 1.6.2 + 1.7.36 test @@ -96,7 +96,7 @@ net.bytebuddy byte-buddy - 1.14.5 + 1.17.5 test @@ -114,7 +114,19 @@ org.seleniumhq.selenium selenium-java - 3.14.0 + 4.13.0 + test + + + com.squareup.okhttp3 + okhttp + 3.14.9 + test + + + org.apache.httpcomponents + httpclient + 4.5.14 test @@ -126,7 +138,7 @@ commons-io commons-io - 2.14.0 + 2.22.0 test @@ -169,7 +181,7 @@ org.revapi revapi-maven-plugin - 0.15.0 + 0.15.1 @@ -183,7 +195,7 @@ org.revapi revapi-java - 0.28.1 + 0.28.4 @@ -199,7 +211,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.5 + 3.5.0 @@ -213,7 +225,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + 3.5.6 @{argLine} -noverify ${skip.unit.tests} @@ -222,7 +234,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.0 + 3.12.0 src/main/java @@ -238,7 +250,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.4.0 attach-sources @@ -251,12 +263,12 @@ com.github.spotbugs spotbugs-maven-plugin - 3.1.11 + 4.2.3 org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.15.0 8 8 @@ -265,7 +277,7 @@ org.codehaus.mojo build-helper-maven-plugin - 1.10 + 3.6.0 add-test-source @@ -284,7 +296,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.5.0 + 3.5.6 @@ -295,6 +307,7 @@ ${skip.integration.tests} + 1 ${adfs.disabled} @@ -303,7 +316,7 @@ biz.aQute.bnd bnd-maven-plugin - 5.2.0 + 6.4.0 @@ -315,7 +328,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 @@ -329,11 +342,38 @@ report + + jacoco-check + verify + + check + + + false + + + BUNDLE + + + LINE + COVEREDRATIO + 0.65 + + + BRANCH + COVEREDRATIO + 0.50 + + + + + + maven-dependency-plugin - 3.1.2 + 3.11.0 diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenInteractiveIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenInteractiveIT.java index 12da8ee3..afd3aa70 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenInteractiveIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenInteractiveIT.java @@ -62,22 +62,6 @@ void acquireTokenInteractive_ADFSv2022() { assertAcquireTokenCommon(user, app.getAppId(), app.getAuthority() + "organizations/", TestConstants.ADFS_SCOPE); } - @Test - void acquireTokenWithAuthorizationCode_B2C_Local() { - AppConfig app = LabResponseHelper.getAppConfig(APP_B2C); - UserConfig user = LabResponseHelper.getUserConfig(USER_B2C); - - assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY, app.getAppId()); - } - - @Test - void acquireTokenWithAuthorizationCode_B2C_LegacyFormat() { - AppConfig app = LabResponseHelper.getAppConfig(APP_B2C); - UserConfig user = LabResponseHelper.getUserConfig(USER_B2C); - - assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY_LEGACY_FORMAT, app.getAppId()); - } - @Test void acquireTokenInteractive_ManagedUser_InstanceAware() { AppConfig app = LabResponseHelper.getAppConfig(APP_ARLINGTON); @@ -86,7 +70,7 @@ void acquireTokenInteractive_ManagedUser_InstanceAware() { assertAcquireTokenInstanceAware(user, app.getAppId(), TestConstants.ARLINGTON_TENANT_ID); } - //@Test -disabled to avoid test failures, HTML page seems to have changed as this test cannot find the username input element + @Test void acquireTokenInteractive_Ciam() { AppConfig app = LabResponseHelper.getAppConfig(APP_CIAM); UserConfig user = LabResponseHelper.getUserConfig(USER_CIAM); @@ -115,7 +99,7 @@ void acquireTokenInteractive_Ciam() { InteractiveRequestParameters parameters = InteractiveRequestParameters .builder(url) - .scopes(Collections.singleton("TestConstants.USER_READ_SCOPE")) + .scopes(Collections.singleton(TestConstants.USER_READ_SCOPE)) .extraQueryParameters(extraQueryParameters) .systemBrowserOptions(browserOptions) .build(); @@ -143,22 +127,6 @@ private void assertAcquireTokenCommon(UserConfig user, String appId, String auth assertEquals(user.getUpn(), result.account().username()); } - private void assertAcquireTokenB2C(UserConfig user, String authority, String appId) { - - PublicClientApplication pca; - try { - pca = PublicClientApplication.builder( - appId). - b2cAuthority(authority + TestConstants.B2C_SIGN_IN_POLICY). - build(); - } catch (MalformedURLException ex) { - throw new RuntimeException(ex.getMessage()); - } - - IAuthenticationResult result = acquireTokenInteractive(user, pca, appId); - IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); - } - private void assertAcquireTokenInstanceAware(UserConfig user, String appId, String tenantId) { PublicClientApplication pca = IntegrationTestHelper.createPublicApp(appId, TestConstants.MICROSOFT_AUTHORITY_HOST + tenantId); diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenSilentIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenSilentIT.java index c2b95926..36379e2c 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenSilentIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AcquireTokenSilentIT.java @@ -29,7 +29,7 @@ void acquireTokenSilent_OrganizationAuthority_TokenRefreshed() throws Exception IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult result = acquireTokenSilently(pca, account, TestConstants.GRAPH_DEFAULT_SCOPE, false); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); } @Test @@ -47,7 +47,7 @@ void acquireTokenSilent_LabAuthority_TokenNotRefreshed() throws Exception { IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult acquireSilentResult = acquireTokenSilently(pca, account, TestConstants.GRAPH_DEFAULT_SCOPE, false); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); // Check that access and id tokens are coming from cache assertEquals(result.accessToken(), acquireSilentResult.accessToken()); @@ -67,14 +67,14 @@ void acquireTokenSilent_ForceRefresh() throws Exception { build(); IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, TestConstants.GRAPH_DEFAULT_SCOPE); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult resultAfterRefresh = acquireTokenSilently(pca, account, TestConstants.GRAPH_DEFAULT_SCOPE, true); - assertResultNotNull(resultAfterRefresh); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(resultAfterRefresh); // Check that new refresh and id tokens are being returned - assertTokensAreNotEqual(result, resultAfterRefresh); + IntegrationTestHelper.assertTokensAreNotEqual(result, resultAfterRefresh); assertEquals(TokenSource.IDENTITY_PROVIDER, result.metadata().tokenSource()); assertEquals(TokenSource.IDENTITY_PROVIDER, resultAfterRefresh.metadata().tokenSource()); } @@ -107,8 +107,7 @@ void acquireTokenSilent_ConfidentialClient_acquireTokenSilent() throws Exception .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result); String cachedAt = result.accessToken(); @@ -133,8 +132,7 @@ void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentScopeThrow .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result); //Acquiring token for different scope, expect exception to be thrown assertThrows(ExecutionException.class, () -> cca.acquireTokenSilently(SilentParameters @@ -154,11 +152,11 @@ void acquireTokenSilent_WithRefreshOn() throws Exception { build(); IAuthenticationResult resultOriginal = acquireTokenUsernamePassword(user, pca, TestConstants.GRAPH_DEFAULT_SCOPE); - assertResultNotNull(resultOriginal); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(resultOriginal); IAuthenticationResult resultSilent = acquireTokenSilently(pca, resultOriginal.account(), TestConstants.GRAPH_DEFAULT_SCOPE, false); assertNotNull(resultSilent); - assertTokensAreEqual(resultOriginal, resultSilent); + IntegrationTestHelper.assertTokensAreEqual(resultOriginal, resultSilent); //When this test was made, token responses did not contain the refresh_in field needed for an end-to-end test. //In order to test silent flow behavior as though the service returned refresh_in, we manually change a cached @@ -174,7 +172,7 @@ void acquireTokenSilent_WithRefreshOn() throws Exception { //Current time is before refreshOn, so token should not have been refreshed assertNotNull(resultSilentWithRefreshOn); assertEquals(pca.tokenCache.accessTokens.get(key).refreshOn(), Long.toString(currTimestampSec + 60)); - assertTokensAreEqual(resultSilent, resultSilentWithRefreshOn); + IntegrationTestHelper.assertTokensAreEqual(resultSilent, resultSilentWithRefreshOn); token = pca.tokenCache.accessTokens.get(key); token.refreshOn(Long.toString(currTimestampSec - 60)); @@ -183,7 +181,7 @@ void acquireTokenSilent_WithRefreshOn() throws Exception { resultSilentWithRefreshOn = acquireTokenSilently(pca, resultOriginal.account(), TestConstants.GRAPH_DEFAULT_SCOPE, false); //Current time is after refreshOn, so token should be refreshed assertNotNull(resultSilentWithRefreshOn); - assertTokensAreNotEqual(resultSilent, resultSilentWithRefreshOn); + IntegrationTestHelper.assertTokensAreNotEqual(resultSilent, resultSilentWithRefreshOn); assertEquals(TokenSource.CACHE, resultSilent.metadata().tokenSource()); assertEquals(TokenSource.IDENTITY_PROVIDER, resultSilentWithRefreshOn.metadata().tokenSource()); } @@ -203,19 +201,19 @@ void acquireTokenSilent_TenantAsParameter() throws Exception { user.getUpn(), user.getPassword().toCharArray()) .build()).get(); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult silentResult = acquireTokenSilently(pca, account, TestConstants.GRAPH_DEFAULT_SCOPE, false); - assertResultNotNull(silentResult); - assertTokensAreEqual(result, silentResult); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(silentResult); + IntegrationTestHelper.assertTokensAreEqual(result, silentResult); IAuthenticationResult resultWithTenantParam = pca.acquireTokenSilently(SilentParameters. builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), account). tenant(user.getTenantId()). build()).get(); - assertResultNotNull(resultWithTenantParam); - assertTokensAreNotEqual(result, resultWithTenantParam); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(resultWithTenantParam); + IntegrationTestHelper.assertTokensAreNotEqual(result, resultWithTenantParam); } @Test @@ -230,11 +228,11 @@ void acquireTokenSilent_emptyStringScope() throws Exception { String emptyScope = StringHelper.EMPTY_STRING; IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, emptyScope); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult silentResult = acquireTokenSilently(pca, account, emptyScope, false); - assertResultNotNull(silentResult); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(silentResult); assertEquals(result.accessToken(), silentResult.accessToken()); } @@ -255,7 +253,7 @@ void acquireTokenSilent_emptyScopeSet() throws Exception { user.getPassword().toCharArray()) .build()) .get(); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult silentResult = pca.acquireTokenSilently(SilentParameters. @@ -263,7 +261,7 @@ void acquireTokenSilent_emptyScopeSet() throws Exception { .build()) .get(); - assertResultNotNull(silentResult); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(silentResult); assertEquals(result.accessToken(), silentResult.accessToken()); } @@ -285,14 +283,14 @@ public void acquireTokenSilent_ClaimsForceRefresh() throws Exception { .build()) .get(); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAuthenticationResult silentResultWithoutClaims = pca.acquireTokenSilently(SilentParameters. builder(scopes, result.account()) .build()) .get(); - assertResultNotNull(silentResultWithoutClaims); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(silentResultWithoutClaims); assertEquals(result.accessToken(), silentResultWithoutClaims.accessToken()); //If claims are added to a silent request, it should trigger the refresh flow and return a new token @@ -305,7 +303,7 @@ public void acquireTokenSilent_ClaimsForceRefresh() throws Exception { .build()) .get(); - assertResultNotNull(silentResultWithClaims); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(silentResultWithClaims); assertNotEquals(result.accessToken(), silentResultWithClaims.accessToken()); } @@ -376,19 +374,4 @@ private IAuthenticationResult acquireTokenUsernamePassword(UserConfig user, IPub .get(); } - private void assertResultNotNull(IAuthenticationResult result) { - assertNotNull(result); - assertNotNull(result.accessToken()); - assertNotNull(result.idToken()); - } - - private void assertTokensAreNotEqual(IAuthenticationResult result, IAuthenticationResult secondResult) { - assertNotEquals(result.accessToken(), secondResult.accessToken()); - assertNotEquals(result.idToken(), secondResult.idToken()); - } - - private void assertTokensAreEqual(IAuthenticationResult result, IAuthenticationResult secondResult) { - assertEquals(result.accessToken(), secondResult.accessToken()); - assertEquals(result.idToken(), secondResult.idToken()); - } } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ApacheHttpClientAdapter.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ApacheHttpClientAdapter.java index 89d6a244..545b86d1 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ApacheHttpClientAdapter.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ApacheHttpClientAdapter.java @@ -41,7 +41,7 @@ public IHttpResponse send(HttpRequest httpRequest) throws Exception { private HttpRequestBase buildApacheRequestFromMsalRequest(HttpRequest httpRequest) { if (httpRequest.httpMethod() == HttpMethod.GET) { - return builGetRequest(httpRequest); + return buildGetRequest(httpRequest); } else if (httpRequest.httpMethod() == HttpMethod.POST) { return buildPostRequest(httpRequest); } else { @@ -49,7 +49,7 @@ private HttpRequestBase buildApacheRequestFromMsalRequest(HttpRequest httpReques } } - private HttpGet builGetRequest(HttpRequest httpRequest) { + private HttpGet buildGetRequest(HttpRequest httpRequest) { HttpGet httpGet = new HttpGet(httpRequest.url().toString()); for (Map.Entry entry : httpRequest.headers().entrySet()) { diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AuthorizationCodeIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AuthorizationCodeIT.java index 58bc72a5..ff7c96fc 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AuthorizationCodeIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/AuthorizationCodeIT.java @@ -45,15 +45,7 @@ public void acquireTokenWithAuthorizationCode_ManagedUser() { assertAcquireTokenAAD(user, app.getAppId(), null); } - //Temporarily disabling: no change in the library, but started seeing "The service has encountered an internal error. Please reauthenticate and try again." - //Needs investigation, tracked in https://github.com/AzureAD/microsoft-authentication-library-for-java/issues/1023 - //@Test - public void acquireTokenWithAuthorizationCode_B2C_Local() { - UserConfig user = LabResponseHelper.getUserConfig(USER_B2C); - assertAcquireTokenB2C(user); - } - - // @Test Disabled, the browser automation suddenly started failing without underlying code changes and needs investigation: https://github.com/AzureAD/microsoft-authentication-library-for-java/issues/1010 + @Test public void acquireTokenWithAuthorizationCode_CiamCud() throws Exception { String authorityCud = "https://login.msidlabsciam.com/fe362aec-5d43-45d1-b730-9755e60dc3b9/v2.0/"; @@ -131,28 +123,6 @@ private void assertAcquireTokenAAD(UserConfig user, String appId, Map", TestHelper.ENCODED_JWT); - ITokenCacheAccessAspect persistenceAspect = new TokenPersistence(dataToInitCache); + ITokenCacheAccessAspect persistenceAspect = new TestTokenCachePersistence(dataToInitCache); PublicClientApplication app = PublicClientApplication.builder("my_client_id") .setTokenCacheAccessAspect(persistenceAspect).build(); diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ClientCredentialsIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ClientCredentialsIT.java index 954c3b50..e2f970ce 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ClientCredentialsIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/ClientCredentialsIT.java @@ -75,8 +75,7 @@ void acquireTokenClientCredentials_Certificate_CiamCud() throws Exception { .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result); } @Test @@ -117,8 +116,7 @@ void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception { .build()) .get(); - assertNotNull(result1); - assertNotNull(result1.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result1); IAuthenticationResult result2 = cca.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) @@ -133,8 +131,7 @@ void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception { .build()) .get(); - assertNotNull(result3); - assertNotNull(result3.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result3); assertNotEquals(result2.accessToken(), result3.accessToken()); } @@ -164,8 +161,7 @@ private void assertAcquireTokenCommon(String clientId, IClientCredential credent .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result); } private void assertAcquireTokenCommon_withParameters(AppConfig app, IClientCredential credential, IClientCredential credentialParam) throws Exception { @@ -180,8 +176,7 @@ private void assertAcquireTokenCommon_withParameters(AppConfig app, IClientCrede .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result); } private void assertAcquireTokenCommon_withRegion(AppConfig app, IClientCredential credential, String region, String regionalAuthority) throws Exception { @@ -202,8 +197,7 @@ private void assertAcquireTokenCommon_withRegion(AppConfig app, IClientCredentia .build()) .get(); - assertNotNull(resultNoRegion); - assertNotNull(resultNoRegion.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(resultNoRegion); assertEquals(TestConstants.MICROSOFT_AUTHORITY_BASIC_HOST, resultNoRegion.environment()); //Ensure regional tokens are properly cached and retrievable @@ -212,17 +206,15 @@ private void assertAcquireTokenCommon_withRegion(AppConfig app, IClientCredentia .build()) .get(); - assertNotNull(resultRegion); - assertNotNull(resultRegion.accessToken()); - assertEquals(resultRegion.environment(), regionalAuthority); + IntegrationTestHelper.assertAccessTokenNotNull(resultRegion); + assertEquals(regionalAuthority, resultRegion.environment()); IAuthenticationResult resultRegionCached = ccaRegion.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) .build()) .get(); - assertNotNull(resultRegionCached); - assertNotNull(resultRegionCached.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(resultRegionCached); assertEquals(resultRegionCached.accessToken(), resultRegion.accessToken()); //Tokens retrieved from regional endpoints should be interchangeable with non-regional, and vice-versa @@ -233,8 +225,7 @@ private void assertAcquireTokenCommon_withRegion(AppConfig app, IClientCredentia .build()) .get(); - assertNotNull(resultNoRegion); - assertNotNull(resultNoRegion.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(resultNoRegion); assertEquals(resultNoRegion.accessToken(), resultRegion.accessToken()); } } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/DeviceCodeIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/DeviceCodeIT.java index 0f205053..eb5bd3b1 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/DeviceCodeIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/DeviceCodeIT.java @@ -6,6 +6,9 @@ import com.microsoft.aad.msal4j.labapi.*; import static com.microsoft.aad.msal4j.labapi.KeyVaultSecrets.*; import infrastructure.SeleniumExtensions; +import infrastructure.SeleniumTestWatcher; +import infrastructure.WebDriverProvider; +import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.WebDriver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,7 +22,8 @@ import java.util.function.Consumer; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class DeviceCodeIT { +@ExtendWith(SeleniumTestWatcher.class) +class DeviceCodeIT implements WebDriverProvider { private static final Logger LOG = LoggerFactory.getLogger(DeviceCodeIT.class); private WebDriver seleniumDriver; @@ -29,9 +33,7 @@ void setUp() { seleniumDriver = SeleniumExtensions.createDefaultWebDriver(); } - //Temporarily disabling: timeout occuring after 15 minutes, likely either a server-side issue or a UI change - //Needs investigation, tracked in https://github.com/AzureAD/microsoft-authentication-library-for-java/issues/1023 - //@Test + @Test void DeviceCodeFlowADTest() throws Exception { AppConfig app = LabResponseHelper.getAppConfig(APP_PCACLIENT); UserConfig user = LabResponseHelper.getUserConfig(USER_PUBLIC_CLOUD); @@ -57,10 +59,15 @@ private void runAutomatedDeviceCodeFlow(DeviceCode deviceCode, UserConfig user) user); } + @Override + public WebDriver getWebDriver() { + return seleniumDriver; + } + @AfterAll void cleanUp() { if (seleniumDriver != null) { - seleniumDriver.close(); + seleniumDriver.quit(); } } } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/HttpClientIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/HttpClientIT.java index fd67946f..674db9bd 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/HttpClientIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/HttpClientIT.java @@ -12,7 +12,6 @@ import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -50,16 +49,10 @@ private void assertAcquireTokenCommon(UserConfig user, String appId, IHttpClient httpClient(httpClient). build(); - IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters. - builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), - user.getUpn(), - user.getPassword().toCharArray()) - .build()) - .get(); + IAuthenticationResult result = IntegrationTestHelper.acquireTokenByRopc( + pca, user, TestConstants.GRAPH_DEFAULT_SCOPE); - assertNotNull(result); - assertNotNull(result.accessToken()); - assertNotNull(result.idToken()); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); assertEquals(user.getUpn(), result.account().username()); } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/IntegrationTestHelper.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/IntegrationTestHelper.java index 04e437d2..19f02ce1 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/IntegrationTestHelper.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/IntegrationTestHelper.java @@ -3,26 +3,112 @@ package com.microsoft.aad.msal4j; +import com.microsoft.aad.msal4j.labapi.UserConfig; + import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; class IntegrationTestHelper { - static PublicClientApplication createPublicApp(String appID, String authority) { + // --- Application builders --- + + static PublicClientApplication createPublicApp(String appId, String authority) { try { - return PublicClientApplication.builder( - appID). - authority(authority). - build(); + return PublicClientApplication.builder(appId) + .authority(authority) + .build(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } + static ConfidentialClientApplication createCca(String clientId, IClientCredential credential, String authority) { + try { + return ConfidentialClientApplication.builder(clientId, credential) + .authority(authority) + .build(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + // --- Token acquisition shortcuts --- + + static IAuthenticationResult acquireTokenByRopc( + PublicClientApplication pca, UserConfig user, Set scopes) + throws ExecutionException, InterruptedException { + return pca.acquireToken(UserNamePasswordParameters + .builder(scopes, user.getUpn(), user.getPassword().toCharArray()) + .build()) + .get(); + } + + static IAuthenticationResult acquireTokenByRopc( + PublicClientApplication pca, UserConfig user, String... scopes) + throws ExecutionException, InterruptedException { + return acquireTokenByRopc(pca, user, toScopeSet(scopes)); + } + + static IAuthenticationResult acquireTokenSilently( + IPublicClientApplication pca, IAccount account, Set scopes) + throws ExecutionException, InterruptedException, MalformedURLException { + return pca.acquireTokenSilently(SilentParameters + .builder(scopes, account) + .build()) + .get(); + } + + static IAuthenticationResult acquireTokenSilently( + IPublicClientApplication pca, IAccount account, String... scopes) + throws ExecutionException, InterruptedException, MalformedURLException { + return acquireTokenSilently(pca, account, toScopeSet(scopes)); + } + + // --- Assertions --- + static void assertAccessAndIdTokensNotNull(IAuthenticationResult result) { assertNotNull(result); assertNotNull(result.accessToken()); assertNotNull(result.idToken()); } + + static void assertAccessTokenNotNull(IAuthenticationResult result) { + assertNotNull(result); + assertNotNull(result.accessToken()); + } + + static void assertAccessTokensEqual(IAuthenticationResult result1, IAuthenticationResult result2) { + assertEquals(result1.accessToken(), result2.accessToken()); + } + + static void assertAccessTokensNotEqual(IAuthenticationResult result1, IAuthenticationResult result2) { + assertNotEquals(result1.accessToken(), result2.accessToken()); + } + + static void assertTokensAreEqual(IAuthenticationResult result1, IAuthenticationResult result2) { + assertEquals(result1.accessToken(), result2.accessToken()); + assertEquals(result1.idToken(), result2.idToken()); + } + + static void assertTokensAreNotEqual(IAuthenticationResult result1, IAuthenticationResult result2) { + assertNotEquals(result1.accessToken(), result2.accessToken()); + assertNotEquals(result1.idToken(), result2.idToken()); + } + + // --- Utilities --- + + private static Set toScopeSet(String... scopes) { + if (scopes.length == 1) { + return Collections.singleton(scopes[0]); + } + return new HashSet<>(Arrays.asList(scopes)); + } } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/OnBehalfOfIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/OnBehalfOfIT.java index 597bf486..15921b2e 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/OnBehalfOfIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/OnBehalfOfIT.java @@ -8,9 +8,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; @@ -41,7 +38,7 @@ void acquireTokenWithOBO_Managed() throws Exception { new UserAssertion(accessToken)).build()). get(); - assertResultNotNull(result); + IntegrationTestHelper.assertAccessTokenNotNull(result); } @Test @@ -62,7 +59,7 @@ void acquireTokenWithOBO_testCache() throws Exception { new UserAssertion(accessToken)).build()). get(); - assertResultNotNull(result1); + IntegrationTestHelper.assertAccessTokenNotNull(result1); // Same scope and userAssertion, should return cached tokens IAuthenticationResult result2 = @@ -71,7 +68,7 @@ void acquireTokenWithOBO_testCache() throws Exception { new UserAssertion(accessToken)).build()). get(); - assertEquals(result1.accessToken(), result2.accessToken()); + IntegrationTestHelper.assertAccessTokensEqual(result1, result2); // Scope 2, should return new token IAuthenticationResult result3 = @@ -80,8 +77,8 @@ void acquireTokenWithOBO_testCache() throws Exception { new UserAssertion(accessToken)).build()). get(); - assertResultNotNull(result3); - assertNotEquals(result2.accessToken(), result3.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result3); + IntegrationTestHelper.assertAccessTokensNotEqual(result2, result3); // Scope 2, should return cached token IAuthenticationResult result4 = @@ -90,7 +87,7 @@ void acquireTokenWithOBO_testCache() throws Exception { new UserAssertion(accessToken)).build()). get(); - assertEquals(result3.accessToken(), result4.accessToken()); + IntegrationTestHelper.assertAccessTokensEqual(result3, result4); // skipCache=true, should return new token IAuthenticationResult result5 = @@ -102,9 +99,9 @@ void acquireTokenWithOBO_testCache() throws Exception { .build()). get(); - assertResultNotNull(result5); - assertNotEquals(result5.accessToken(), result4.accessToken()); - assertNotEquals(result5.accessToken(), result2.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result5); + IntegrationTestHelper.assertAccessTokensNotEqual(result5, result4); + IntegrationTestHelper.assertAccessTokensNotEqual(result5, result2); String newAccessToken = this.getAccessToken(); @@ -117,15 +114,10 @@ void acquireTokenWithOBO_testCache() throws Exception { .build()). get(); - assertResultNotNull(result6); - assertNotEquals(result6.accessToken(), result5.accessToken()); - assertNotEquals(result6.accessToken(), result4.accessToken()); - assertNotEquals(result6.accessToken(), result2.accessToken()); - } - - private void assertResultNotNull(IAuthenticationResult result) { - assertNotNull(result); - assertNotNull(result.accessToken()); + IntegrationTestHelper.assertAccessTokenNotNull(result6); + IntegrationTestHelper.assertAccessTokensNotEqual(result6, result5); + IntegrationTestHelper.assertAccessTokensNotEqual(result6, result4); + IntegrationTestHelper.assertAccessTokensNotEqual(result6, result2); } private String getAccessToken() throws Exception { diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/RefreshTokenIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/RefreshTokenIT.java index a0e14db6..a4113228 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/RefreshTokenIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/RefreshTokenIT.java @@ -7,7 +7,6 @@ import static com.microsoft.aad.msal4j.labapi.KeyVaultSecrets.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Collections; @@ -47,9 +46,7 @@ void acquireTokenWithRefreshToken() throws Exception { .build()) .get(); - assertNotNull(result); - assertNotNull(result.accessToken()); - assertNotNull(result.idToken()); + IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); } @Test diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/SeleniumTest.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/SeleniumTest.java index 922c03c4..a9a6f396 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/SeleniumTest.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/SeleniumTest.java @@ -5,11 +5,15 @@ import com.microsoft.aad.msal4j.labapi.UserConfig; import infrastructure.SeleniumExtensions; +import infrastructure.SeleniumTestWatcher; +import infrastructure.WebDriverProvider; +import org.junit.jupiter.api.extension.ExtendWith; import org.openqa.selenium.WebDriver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class SeleniumTest { +@ExtendWith(SeleniumTestWatcher.class) +abstract class SeleniumTest implements WebDriverProvider { private static final Logger LOG = LoggerFactory.getLogger(SeleniumTest.class); WebDriver seleniumDriver; @@ -36,14 +40,16 @@ public void startUpBrowser() { seleniumDriver = SeleniumExtensions.createDefaultWebDriver(); } + @Override + public WebDriver getWebDriver() { + return seleniumDriver; + } + void runSeleniumAutomatedLogin(UserConfig user, AbstractClientApplicationBase app) { AuthorityType authorityType = app.authenticationAuthority.authorityType; try { switch (authorityType) { - case B2C: - SeleniumExtensions.performLocalLogin(seleniumDriver, user); - break; case AAD: SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); break; @@ -51,8 +57,17 @@ void runSeleniumAutomatedLogin(UserConfig user, AbstractClientApplicationBase ap SeleniumExtensions.performADFSLogin(seleniumDriver, user); break; case CIAM: + SeleniumExtensions.performCiamLogin(seleniumDriver, user); + break; case OIDC: - SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); + // OIDC authorities may use CIAM or AAD login pages depending on the host. + // Check the authority host to determine which page object to use. + if (app.authenticationAuthority.host.contains("ciam") || + app.authenticationAuthority.host.contains("msidlabsciam")) { + SeleniumExtensions.performCiamLogin(seleniumDriver, user); + } else { + SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); + } break; default: throw new IllegalArgumentException("Unsupported authority type: " + authorityType); diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestConstants.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestConstants.java index ce06f8d8..f2f2d63b 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestConstants.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestConstants.java @@ -8,9 +8,6 @@ public class TestConstants { public static final String GRAPH_DEFAULT_SCOPE = "https://graph.windows.net/.default"; public static final String USER_READ_SCOPE = "user.read"; public static final String DEFAULT_SCOPE = ".default"; - public static final String B2C_LAB_SCOPE = "https://msidlabb2c.onmicrosoft.com/msaapp/user_impersonation"; - public static final String B2C_CONFIDENTIAL_CLIENT_APP_SECRETID = "MSIDLABB2C-MSAapp-AppSecret"; - public static final String B2C_CONFIDENTIAL_CLIENT_LAB_APP_ID = "MSIDLABB2C-MSAapp-AppID"; public static final String MICROSOFT_AUTHORITY_HOST = "https://login.microsoftonline.com/"; public static final String MICROSOFT_AUTHORITY_BASIC_HOST = "login.microsoftonline.com"; @@ -23,16 +20,12 @@ public class TestConstants { public static final String REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS = "westus.login.microsoft.com"; public static final String B2C_AUTHORITY = "https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/"; - public static final String B2C_AUTHORITY_LEGACY_FORMAT = "https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/"; public static final String B2C_ROPC_POLICY = "B2C_1_ROPC_Auth"; - public static final String B2C_SIGN_IN_POLICY = "B2C_1_SignInPolicy"; - public static final String B2C_AUTHORITY_SIGN_IN = B2C_AUTHORITY + B2C_SIGN_IN_POLICY; public static final String B2C_AUTHORITY_ROPC = B2C_AUTHORITY + B2C_ROPC_POLICY; public static final String B2C_READ_SCOPE = "https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"; public static final String B2C_MICROSOFTLOGIN_AUTHORITY = "https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/"; public static final String B2C_MICROSOFTLOGIN_ROPC = B2C_MICROSOFTLOGIN_AUTHORITY + B2C_ROPC_POLICY; - public static final String B2C_UPN = "b2clocal@msidlabb2c.onmicrosoft.com"; public static final String LOCALHOST = "http://localhost:"; diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestTokenCachePersistence.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestTokenCachePersistence.java new file mode 100644 index 00000000..f6955ea7 --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TestTokenCachePersistence.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j; + +/** + * Simple in-memory token cache persistence for integration tests. + * Implements ITokenCacheAccessAspect to serialize/deserialize cache data + * across application instances. + */ +class TestTokenCachePersistence implements ITokenCacheAccessAspect { + String data; + + TestTokenCachePersistence(String data) { + this.data = data; + } + + @Override + public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + iTokenCacheAccessContext.tokenCache().deserialize(data); + } + + @Override + public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + data = iTokenCacheAccessContext.tokenCache().serialize(); + } +} diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TokenCacheIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TokenCacheIT.java index 570c289a..2295a43e 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TokenCacheIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/TokenCacheIT.java @@ -29,7 +29,7 @@ void singleAccountInCache_RemoveAccountTest() throws Exception { build(); // Check that cache is empty - assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(0, pca.getAccounts().join().size()); Map extraQueryParameters = new HashMap<>(); extraQueryParameters.put("test", "test"); @@ -43,12 +43,12 @@ void singleAccountInCache_RemoveAccountTest() throws Exception { .get(); // Check that cache contains one account - assertEquals(pca.getAccounts().join().size(), 1); + assertEquals(1, pca.getAccounts().join().size()); pca.removeAccount(pca.getAccounts().join().iterator().next()).join(); // Check that account has been removed - assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(0, pca.getAccounts().join().size()); } @Test @@ -65,7 +65,7 @@ void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() throws Exce // check that cache is empty assertEquals(dataToInitCache, ""); - ITokenCacheAccessAspect persistenceAspect = new TokenPersistence(dataToInitCache); + ITokenCacheAccessAspect persistenceAspect = new TestTokenCachePersistence(dataToInitCache); // acquire tokens for home tenant, and serialize cache PublicClientApplication pca = PublicClientApplication.builder( @@ -112,22 +112,4 @@ void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() throws Exce this.getClass(), "/cache_data/remove-account-test-cache.json"); } - - private static class TokenPersistence implements ITokenCacheAccessAspect { - String data; - - TokenPersistence(String data) { - this.data = data; - } - - @Override - public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - iTokenCacheAccessContext.tokenCache().deserialize(data); - } - - @Override - public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - data = iTokenCacheAccessContext.tokenCache().serialize(); - } - } } diff --git a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/UsernamePasswordIT.java b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/UsernamePasswordIT.java index f2ee9121..9b45dc41 100644 --- a/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/UsernamePasswordIT.java +++ b/msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/UsernamePasswordIT.java @@ -59,17 +59,9 @@ void acquireTokenWithUsernamePassword_Ciam() throws Exception { private void assertAcquireTokenCommon(UserConfig user, String authority, String scope, String appId) throws Exception { - PublicClientApplication pca = PublicClientApplication.builder( - appId). - authority(authority). - build(); + PublicClientApplication pca = IntegrationTestHelper.createPublicApp(appId, authority); - IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters. - builder(Collections.singleton(scope), - user.getUpn(), - user.getPassword().toCharArray()) - .build()) - .get(); + IAuthenticationResult result = IntegrationTestHelper.acquireTokenByRopc(pca, user, scope); IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); assertEquals(user.getUpn(), result.account().username()); @@ -95,7 +87,6 @@ void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Exception { IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); - SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account); result = pca.acquireTokenSilently( SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account) @@ -125,7 +116,6 @@ void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws Exceptio IntegrationTestHelper.assertAccessAndIdTokensNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); - SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account); result = pca.acquireTokenSilently( SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account) diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumDiagnostics.java b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumDiagnostics.java new file mode 100644 index 00000000..996d139f --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumDiagnostics.java @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package infrastructure; + +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.logging.LogEntry; +import org.openqa.selenium.logging.LogType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * Utility for capturing diagnostic information from Selenium WebDriver on test failure. + *

+ * Captures screenshots, page source (HTML), and browser console logs to help diagnose + * failures in browser-based integration tests. All capture methods are fail-safe and will + * not throw exceptions, so they can be called safely during test teardown without masking + * the original test failure. + *

+ * Output is written to {@code target/selenium-diagnostics/} with filenames that include + * the test name and a timestamp for uniqueness. + */ +public final class SeleniumDiagnostics { + + private static final Logger LOG = LoggerFactory.getLogger(SeleniumDiagnostics.class); + private static final String OUTPUT_DIR = "target/selenium-diagnostics"; + + private SeleniumDiagnostics() { + } + + /** + * Capture all available diagnostics (screenshot, page source, browser logs) for a failed test. + * + * @param driver the WebDriver instance (may be null) + * @param testName the name of the test method that failed + */ + public static void captureAll(WebDriver driver, String testName) { + if (driver == null) { + LOG.warn("Cannot capture diagnostics: WebDriver is null"); + return; + } + + String sanitizedName = sanitizeFileName(testName); + String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()); + String filePrefix = sanitizedName + "-" + timestamp; + + captureScreenshot(driver, filePrefix); + capturePageSource(driver, filePrefix); + captureBrowserLogs(driver, filePrefix); + } + + /** + * Capture a screenshot of the current browser state. + * + * @param driver the WebDriver instance + * @param filePrefix the file name prefix (test name + timestamp) + */ + static void captureScreenshot(WebDriver driver, String filePrefix) { + try { + if (!(driver instanceof TakesScreenshot)) { + LOG.warn("WebDriver does not support screenshots"); + return; + } + + File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); + Path destination = getOutputPath(filePrefix + ".png"); + Files.copy(screenshot.toPath(), destination, StandardCopyOption.REPLACE_EXISTING); + LOG.info("Screenshot saved: {}", destination); + } catch (Exception e) { + LOG.warn("Failed to capture screenshot: {}", e.getMessage()); + } + } + + /** + * Capture the current page source (HTML) for element inspection. + *

+ * Sensitive parameters (auth codes, tokens, etc.) in URLs and form actions are redacted. + * + * @param driver the WebDriver instance + * @param filePrefix the file name prefix (test name + timestamp) + */ + static void capturePageSource(WebDriver driver, String filePrefix) { + try { + String pageSource = driver.getPageSource(); + if (pageSource == null || pageSource.isEmpty()) { + LOG.warn("Page source is empty"); + return; + } + + String currentUrl = driver.getCurrentUrl(); + String redactedUrl = redactSensitiveParams(currentUrl); + + StringBuilder content = new StringBuilder(); + content.append("\n"); + content.append("\n"); + content.append(pageSource); + + Path destination = getOutputPath(filePrefix + ".html"); + Files.write(destination, content.toString().getBytes("UTF-8")); + LOG.info("Page source saved: {}", destination); + } catch (Exception e) { + LOG.warn("Failed to capture page source: {}", e.getMessage()); + } + } + + /** + * Capture browser console logs (JavaScript errors, network issues, etc.). + * + * @param driver the WebDriver instance + * @param filePrefix the file name prefix (test name + timestamp) + */ + static void captureBrowserLogs(WebDriver driver, String filePrefix) { + try { + List logs = driver.manage().logs().get(LogType.BROWSER).getAll(); + + if (logs.isEmpty()) { + LOG.debug("No browser console logs to capture"); + return; + } + + StringBuilder content = new StringBuilder(); + content.append("Browser Console Logs\n"); + content.append("====================\n\n"); + + for (LogEntry entry : logs) { + content.append("[").append(entry.getLevel()).append("] "); + content.append(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date(entry.getTimestamp()))); + content.append(" - ").append(redactSensitiveParams(entry.getMessage())); + content.append("\n"); + } + + Path destination = getOutputPath(filePrefix + "-console.log"); + Files.write(destination, content.toString().getBytes("UTF-8")); + LOG.info("Browser logs saved: {}", destination); + } catch (Exception e) { + LOG.warn("Failed to capture browser logs: {} (this may be expected if logging prefs are not supported)", e.getMessage()); + } + } + + /** + * Get the output path for a diagnostic file, creating the directory if needed. + */ + private static Path getOutputPath(String fileName) throws IOException { + Path dir = Paths.get(OUTPUT_DIR); + if (!Files.exists(dir)) { + Files.createDirectories(dir); + } + return dir.resolve(fileName); + } + + /** + * Sanitize a test name to be safe for use as a file name on all platforms. + * Removes/replaces characters that are invalid in Windows file paths. + */ + private static String sanitizeFileName(String name) { + if (name == null) { + return "unknown"; + } + return name.replaceAll("[^a-zA-Z0-9._-]", "_"); + } + + /** + * Redact sensitive OAuth parameters from URLs and log messages. + * Replaces values of known sensitive query parameters with "[REDACTED]". + */ + public static String redactSensitiveParams(String text) { + if (text == null) { + return ""; + } + return text.replaceAll( + "(?i)(code|access_token|id_token|refresh_token|client_secret|password|assertion)=([^&\\s\"']*)", + "$1=[REDACTED]"); + } +} diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java index 7a171b05..25bcb8da 100644 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java @@ -6,18 +6,21 @@ import com.microsoft.aad.msal4j.labapi.UserConfig; import infrastructure.pageobjects.ADFSLoginPage; import infrastructure.pageobjects.AzureADLoginPage; -import infrastructure.pageobjects.B2CLocalLoginPage; +import infrastructure.pageobjects.CIAMLoginPage; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.logging.LogType; +import org.openqa.selenium.logging.LoggingPreferences; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; +import java.util.logging.Level; public class SeleniumExtensions { @@ -28,17 +31,67 @@ private SeleniumExtensions() { public static WebDriver createDefaultWebDriver() { ChromeOptions options = new ChromeOptions(); - options.addArguments("--headless"); + options.addArguments("--headless=new"); options.addArguments("--incognito"); + // Enable browser console logging for diagnostic capture on failure + LoggingPreferences logPrefs = new LoggingPreferences(); + logPrefs.enable(LogType.BROWSER, Level.ALL); + options.setCapability("goog:loggingPrefs", logPrefs); + return new ChromeDriver(options); } public static WebElement waitForElementToBeVisibleAndEnabled(WebDriver driver, By by, Duration timeout) { - WebDriverWait wait = new WebDriverWait(driver, timeout.getSeconds()); + WebDriverWait wait = new WebDriverWait(driver, timeout); return wait.until(ExpectedConditions.elementToBeClickable(by)); } + /** + * Wait for any one of several locators to match a clickable element, returning the first match. + *

+ * This uses a single wait loop that checks all locators on each poll cycle, so the total + * wait time is bounded by {@code timeout} regardless of how many locators are provided. + * When a fallback locator matches (not the primary), a warning is logged to flag that + * the page structure may have changed. + * + * @param driver the WebDriver instance + * @param timeout maximum time to wait for any locator to match + * @param locators one or more locators to try, in priority order (primary first) + * @return the first clickable WebElement found + * @throws org.openqa.selenium.TimeoutException if no locator matches within the timeout + */ + public static WebElement findWithFallback(WebDriver driver, Duration timeout, By... locators) { + if (locators.length == 0) { + throw new IllegalArgumentException("At least one locator must be provided"); + } + + if (locators.length == 1) { + return waitForElementToBeVisibleAndEnabled(driver, locators[0], timeout); + } + + WebDriverWait wait = new WebDriverWait(driver, timeout); + final By primaryLocator = locators[0]; + + return wait.until(d -> { + for (By locator : locators) { + try { + WebElement element = d.findElement(locator); + if (element != null && element.isDisplayed() && element.isEnabled()) { + if (!locator.equals(primaryLocator)) { + LOG.warn("Primary locator {} not found, matched fallback: {}", + primaryLocator, locator); + } + return element; + } + } catch (Exception ignored) { + // Element not found with this locator, try next + } + } + return null; + }); + } + public static void performADOrCiamLogin(WebDriver driver, UserConfig user) { LOG.info("performADOrCiamLogin for user: {}", user.getUpn()); @@ -53,10 +106,10 @@ public static void performADFSLogin(WebDriver driver, UserConfig user) { loginPage.login(user.getUpn(), user.getPassword()); } - public static void performLocalLogin(WebDriver driver, UserConfig user) { - LOG.info("performLocalLogin"); + public static void performCiamLogin(WebDriver driver, UserConfig user) { + LOG.info("performCiamLogin for user: {}", user.getUpn()); - B2CLocalLoginPage loginPage = new B2CLocalLoginPage(driver); + CIAMLoginPage loginPage = new CIAMLoginPage(driver); loginPage.login(user.getUpn(), user.getPassword()); } diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumTestWatcher.java b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumTestWatcher.java new file mode 100644 index 00000000..972054dc --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumTestWatcher.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package infrastructure; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.openqa.selenium.WebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JUnit 5 extension that captures Selenium diagnostics (screenshot, page source, browser logs) + * when a test fails. + *

+ * This extension uses {@link AfterTestExecutionCallback} which runs after the test method + * but before {@code @AfterEach} teardown. This ordering is critical because it ensures + * the WebDriver is still alive when diagnostics are captured — the driver is typically closed + * in {@code @AfterEach}. + *

+ * Test classes must implement {@link WebDriverProvider} to expose their WebDriver instance. + * If the test class does not implement the interface, or the driver is null, diagnostics + * are silently skipped. + *

+ * Usage: + *

+ * {@literal @}ExtendWith(SeleniumTestWatcher.class)
+ * class MySeleniumTest implements WebDriverProvider {
+ *     WebDriver driver;
+ *
+ *     public WebDriver getWebDriver() { return driver; }
+ * }
+ * 
+ * + * @see SeleniumDiagnostics + * @see WebDriverProvider + */ +public class SeleniumTestWatcher implements AfterTestExecutionCallback { + + private static final Logger LOG = LoggerFactory.getLogger(SeleniumTestWatcher.class); + + @Override + public void afterTestExecution(ExtensionContext context) { + // Only capture diagnostics if the test failed + if (!context.getExecutionException().isPresent()) { + return; + } + + Object testInstance = context.getTestInstance().orElse(null); + if (!(testInstance instanceof WebDriverProvider)) { + LOG.debug("Test class does not implement WebDriverProvider, skipping diagnostics"); + return; + } + + WebDriver driver = ((WebDriverProvider) testInstance).getWebDriver(); + if (driver == null) { + LOG.warn("WebDriver is null, cannot capture diagnostics for failed test: {}", + context.getDisplayName()); + return; + } + + String testName = context.getTestClass() + .map(Class::getSimpleName) + .orElse("UnknownClass") + + "." + context.getDisplayName(); + + LOG.info("Test failed: {} — capturing diagnostics", testName); + SeleniumDiagnostics.captureAll(driver, testName); + } +} diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/WebDriverProvider.java b/msal4j-sdk/src/integrationtest/java/infrastructure/WebDriverProvider.java new file mode 100644 index 00000000..dec68801 --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/WebDriverProvider.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package infrastructure; + +import org.openqa.selenium.WebDriver; + +/** + * Interface for test classes that manage a WebDriver instance. + * Used by {@link SeleniumTestWatcher} to access the driver for diagnostics on test failure. + */ +public interface WebDriverProvider { + + /** + * Returns the current WebDriver instance, or null if the driver has not been initialized + * or has already been closed. + * + * @return the WebDriver instance, or null + */ + WebDriver getWebDriver(); +} diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/ADFSLoginPage.java b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/ADFSLoginPage.java index ec7ccd0a..aaa30a4e 100644 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/ADFSLoginPage.java +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/ADFSLoginPage.java @@ -3,10 +3,9 @@ package infrastructure.pageobjects; +import infrastructure.SeleniumExtensions; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,24 +14,37 @@ /** * Page Object Model for ADFS login page. * Represents the Active Directory Federation Services authentication flow. + *

+ * Uses fallback locators to handle ADFS version differences where element IDs + * may vary between ADFS 2019, 2022, and other versions. */ public class ADFSLoginPage { private static final Logger LOG = LoggerFactory.getLogger(ADFSLoginPage.class); private final WebDriver driver; - private final WebDriverWait wait; - // Element locators - private static final By USERNAME_INPUT = By.id("userNameInput"); - private static final By PASSWORD_INPUT = By.id("passwordInput"); - private static final By SUBMIT_BUTTON = By.id("submitButton"); + // Element locators with fallbacks for different ADFS versions + private static final By[] USERNAME_LOCATORS = { + By.id("userNameInput"), + By.id("ContentPlaceHolder1_UsernameTextBox"), + By.cssSelector("input[type='text'][name='UserName']"), + }; + private static final By[] PASSWORD_LOCATORS = { + By.id("passwordInput"), + By.id("ContentPlaceHolder1_PasswordTextBox"), + By.cssSelector("input[type='password'][name='Password']"), + }; + private static final By[] SUBMIT_LOCATORS = { + By.id("submitButton"), + By.id("ContentPlaceHolder1_SubmitButton"), + By.cssSelector("span[id='submitButton']"), + }; private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(15); public ADFSLoginPage(WebDriver driver) { this.driver = driver; - this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT.getSeconds()); } /** @@ -43,7 +55,7 @@ public ADFSLoginPage(WebDriver driver) { */ public ADFSLoginPage enterUsername(String username) { LOG.info("Entering username: {}", username); - wait.until(ExpectedConditions.elementToBeClickable(USERNAME_INPUT)) + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, USERNAME_LOCATORS) .sendKeys(username); return this; } @@ -56,7 +68,7 @@ public ADFSLoginPage enterUsername(String username) { */ public ADFSLoginPage enterPassword(String password) { LOG.info("Entering password"); - wait.until(ExpectedConditions.elementToBeClickable(PASSWORD_INPUT)) + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, PASSWORD_LOCATORS) .sendKeys(password); return this; } @@ -68,7 +80,7 @@ public ADFSLoginPage enterPassword(String password) { */ public ADFSLoginPage clickSubmit() { LOG.info("Clicking submit button"); - wait.until(ExpectedConditions.elementToBeClickable(SUBMIT_BUTTON)) + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, SUBMIT_LOCATORS) .click(); return this; } diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/AzureADLoginPage.java b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/AzureADLoginPage.java index dcaab635..69b609ad 100644 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/AzureADLoginPage.java +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/AzureADLoginPage.java @@ -3,6 +3,7 @@ package infrastructure.pageobjects; +import infrastructure.SeleniumExtensions; import org.openqa.selenium.By; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebDriver; @@ -16,6 +17,9 @@ /** * Page Object Model for Azure AD login page. * Represents the standard Azure AD authentication flow. + *

+ * Uses fallback locators for username/password fields to handle variations + * in the Azure AD login page across different tenants and configurations. */ public class AzureADLoginPage { @@ -24,9 +28,17 @@ public class AzureADLoginPage { private final WebDriver driver; private final WebDriverWait wait; - // Element locators - private static final By USERNAME_INPUT = By.id("i0116"); - private static final By PASSWORD_INPUT = By.id("i0118"); + // Element locators with fallbacks + private static final By[] USERNAME_LOCATORS = { + By.id("i0116"), + By.name("loginfmt"), + By.cssSelector("input[type='email'][name='loginfmt']"), + }; + private static final By[] PASSWORD_LOCATORS = { + By.id("i0118"), + By.name("passwd"), + By.cssSelector("input[type='password'][name='passwd']"), + }; private static final By NEXT_BUTTON = By.id("idSIButton9"); private static final By SUBMIT_BUTTON = By.id("idSIButton9"); @@ -43,7 +55,7 @@ public class AzureADLoginPage { public AzureADLoginPage(WebDriver driver) { this.driver = driver; - this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT.getSeconds()); + this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT); } /** @@ -54,7 +66,7 @@ public AzureADLoginPage(WebDriver driver) { */ public AzureADLoginPage enterUsername(String username) { LOG.info("Entering username: {}", username); - wait.until(ExpectedConditions.elementToBeClickable(USERNAME_INPUT)) + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, USERNAME_LOCATORS) .sendKeys(username); return this; } @@ -79,7 +91,7 @@ public AzureADLoginPage clickNext() { */ public AzureADLoginPage enterPassword(String password) { LOG.info("Entering password"); - wait.until(ExpectedConditions.elementToBeClickable(PASSWORD_INPUT)) + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, PASSWORD_LOCATORS) .sendKeys(password); return this; } @@ -103,7 +115,7 @@ public AzureADLoginPage clickSubmit() { */ public boolean isAuthenticationComplete() { try { - WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT.getSeconds()); + WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT); shortWait.until(ExpectedConditions.textToBePresentInElementLocated( AUTH_COMPLETE_BODY, AUTH_COMPLETE_TEXT)); LOG.info("Authentication complete page detected"); @@ -150,7 +162,7 @@ private void handleOptionalPrompts() { private void handleAreYouTryingToSignInPrompt() { try { LOG.info("Checking for 'Are you trying to sign in' prompt"); - WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT.getSeconds()); + WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT); shortWait.until(ExpectedConditions.elementToBeClickable(ARE_YOU_TRYING_TO_SIGN_IN_BUTTON)) .click(); LOG.info("Clicked Continue on 'Are you trying to sign in' prompt"); @@ -165,7 +177,7 @@ private void handleAreYouTryingToSignInPrompt() { private void handleStaySignedInPrompt() { try { LOG.info("Checking for 'Stay signed in' prompt"); - WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT.getSeconds()); + WebDriverWait shortWait = new WebDriverWait(driver, SHORT_TIMEOUT); shortWait.until(ExpectedConditions.elementToBeClickable(STAY_SIGNED_IN_NO_BUTTON)) .click(); LOG.info("Clicked No on 'Stay signed in' prompt"); diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/B2CLocalLoginPage.java b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/B2CLocalLoginPage.java deleted file mode 100644 index 98bbf182..00000000 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/B2CLocalLoginPage.java +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package infrastructure.pageobjects; - -import com.microsoft.aad.msal4j.TestConstants; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; - -/** - * Page Object Model for B2C Local Account login page. - * Represents the Azure AD B2C local account authentication flow. - */ -public class B2CLocalLoginPage { - - private static final Logger LOG = LoggerFactory.getLogger(B2CLocalLoginPage.class); - - private final WebDriver driver; - private final WebDriverWait wait; - - // Element locators - private static final By LOCAL_ACCOUNT_BUTTON = By.id("SignInWithLogonNameExchange"); - private static final By USERNAME_INPUT = By.id("cred_userid_inputtext"); - private static final By PASSWORD_INPUT = By.id("cred_password_inputtext"); - private static final By SIGN_IN_BUTTON = By.id("cred_sign_in_button"); - - private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(15); - - public B2CLocalLoginPage(WebDriver driver) { - this.driver = driver; - this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT.getSeconds()); - } - - /** - * Click the local account sign-in option. - * - * @return This page object for method chaining - */ - public B2CLocalLoginPage clickLocalAccount() { - LOG.info("Clicking local account button"); - wait.until(ExpectedConditions.elementToBeClickable(LOCAL_ACCOUNT_BUTTON)) - .click(); - return this; - } - - /** - * Enter the username in the B2C login page. - * - * @param username The username to enter - * @return This page object for method chaining - */ - public B2CLocalLoginPage enterUsername(String username) { - LOG.info("Entering username: {}", TestConstants.B2C_UPN); - wait.until(ExpectedConditions.elementToBeClickable(USERNAME_INPUT)) - .sendKeys(TestConstants.B2C_UPN); - return this; - } - - /** - * Enter the password in the B2C login page. - * - * @param password The password to enter - * @return This page object for method chaining - */ - public B2CLocalLoginPage enterPassword(String password) { - LOG.info("Entering password"); - wait.until(ExpectedConditions.elementToBeClickable(PASSWORD_INPUT)) - .sendKeys(password); - return this; - } - - /** - * Click the Sign in button to complete login. - * - * @return This page object for method chaining - */ - public B2CLocalLoginPage clickSignIn() { - LOG.info("Clicking sign in button"); - wait.until(ExpectedConditions.elementToBeClickable(SIGN_IN_BUTTON)) - .click(); - return this; - } - - /** - * Perform a complete B2C local account login flow. - * This is a convenience method that chains all the necessary steps. - * - * @param username The username - * @param password The password - */ - public void login(String username, String password) { - clickLocalAccount() - .enterUsername(username) - .enterPassword(password) - .clickSignIn(); - - LOG.info("B2C local login completed"); - } -} diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/CIAMLoginPage.java b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/CIAMLoginPage.java new file mode 100644 index 00000000..6fc26ced --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/pageobjects/CIAMLoginPage.java @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package infrastructure.pageobjects; + +import infrastructure.SeleniumExtensions; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; + +/** + * Page Object Model for CIAM (Customer Identity and Access Management) login page. + * Represents the Azure AD CIAM authentication flow, which uses a multi-step form + * with different element IDs than the standard Azure AD login page. + *

+ * CIAM login flow: + * 1. Enter email address + * 2. Click Next + * 3. Enter password + * 4. Click Sign in + *

+ * Uses fallback locators to handle variations in CIAM tenant configurations. + */ +public class CIAMLoginPage { + + private static final Logger LOG = LoggerFactory.getLogger(CIAMLoginPage.class); + + private final WebDriver driver; + + // Username step locators + private static final By[] USERNAME_LOCATORS = { + By.name("username"), + By.cssSelector("input[type='text'][autocomplete*='username']"), + By.cssSelector("input[type='email']"), + }; + private static final By[] NEXT_BUTTON_LOCATORS = { + By.id("usernamePrimaryButton"), + By.cssSelector("button[type='submit'][aria-label='Next']"), + By.cssSelector("button.ext-primary[type='submit']"), + }; + + // Password step locators + private static final By[] PASSWORD_LOCATORS = { + By.name("password"), + By.id("password"), + By.cssSelector("input[type='password']"), + }; + private static final By[] SIGN_IN_BUTTON_LOCATORS = { + By.id("passwordPrimaryButton"), + By.cssSelector("button[type='submit'][aria-label='Sign in']"), + By.cssSelector("button.ext-primary[type='submit']"), + }; + + private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(15); + + public CIAMLoginPage(WebDriver driver) { + this.driver = driver; + } + + /** + * Enter the username/email in the CIAM login page. + * + * @param username The email address to enter + * @return This page object for method chaining + */ + public CIAMLoginPage enterUsername(String username) { + LOG.info("Entering username: {}", username); + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, USERNAME_LOCATORS) + .sendKeys(username); + return this; + } + + /** + * Click the Next button after entering the username. + * + * @return This page object for method chaining + */ + public CIAMLoginPage clickNext() { + LOG.info("Clicking Next button"); + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, NEXT_BUTTON_LOCATORS) + .click(); + return this; + } + + /** + * Enter the password in the CIAM login page. + * + * @param password The password to enter + * @return This page object for method chaining + */ + public CIAMLoginPage enterPassword(String password) { + LOG.info("Entering password"); + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, PASSWORD_LOCATORS) + .sendKeys(password); + return this; + } + + /** + * Click the Sign in button to complete login. + * + * @return This page object for method chaining + */ + public CIAMLoginPage clickSignIn() { + LOG.info("Clicking Sign in button"); + SeleniumExtensions.findWithFallback(driver, DEFAULT_TIMEOUT, SIGN_IN_BUTTON_LOCATORS) + .click(); + return this; + } + + /** + * Perform a complete CIAM login flow. + * This is a convenience method that chains all the necessary steps. + * + * @param username The email address + * @param password The password + */ + public void login(String username, String password) { + enterUsername(username) + .clickNext() + .enterPassword(password) + .clickSignIn(); + + LOG.info("CIAM login completed"); + } +} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java index f28aec53..12123d93 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java @@ -138,9 +138,9 @@ void aadInstanceDiscoveryTest_AutoDetectRegion_NoRegionDetected() throws Excepti } void assertValidResponse(InstanceDiscoveryMetadataEntry entry) { - assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); - assertEquals(entry.preferredCache(), "login.windows.net"); - assertEquals(entry.aliases().size(), 4); + assertEquals("login.microsoftonline.com", entry.preferredNetwork()); + assertEquals("login.windows.net", entry.preferredCache()); + assertEquals(4, entry.aliases().size()); assertTrue(entry.aliases().contains("login.microsoftonline.com")); assertTrue(entry.aliases().contains("login.windows.net")); assertTrue(entry.aliases().contains("login.microsoft.com")); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java index 10807817..60b5f434 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java @@ -91,22 +91,22 @@ ITokenCacheAccessAspect init(String data) { Set accounts = pca.getAccounts().join(); - assertEquals(accounts.size(), 1); + assertEquals(1, accounts.size()); IAccount account = accounts.iterator().next(); Map tenantProfiles = account.getTenantProfiles(); - assertEquals(tenantProfiles.size(), 2); + assertEquals(2, tenantProfiles.size()); assertTrue(tenantProfiles.containsKey(BLACK_FORESRT_TENANT)); assertTrue(tenantProfiles.containsKey(WW_TENTANT)); pca.removeAccount(account).join(); accounts = pca.getAccounts().join(); - assertEquals(accounts.size(), 0); + assertEquals(0, accounts.size()); - assertEquals(pca.tokenCache.accounts.size(), 0); - assertEquals(pca.tokenCache.idTokens.size(), 0); - assertEquals(pca.tokenCache.refreshTokens.size(), 0); - assertEquals(pca.tokenCache.accessTokens.size(), 0); + assertEquals(0, pca.tokenCache.accounts.size()); + assertEquals(0, pca.tokenCache.idTokens.size()); + assertEquals(0, pca.tokenCache.refreshTokens.size()); + assertEquals(0, pca.tokenCache.accessTokens.size()); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java index f957a079..aae1214d 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java @@ -13,10 +13,6 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.mockito.Mockito.*; -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.concurrent.CompletableFuture; @@ -28,7 +24,7 @@ class AcquireTokenSilentlyTest { Account basicAccount = new Account("home_account_id", "login.windows.net", "username", null); - String cache = readResource("/AAD_cache_data/full_cache.json"); + String cache = TestHelper.readResource(this.getClass(), "/AAD_cache_data/full_cache.json"); @Test void publicAppAcquireTokenSilently_emptyCache_MsalClientException() throws Throwable { @@ -209,12 +205,4 @@ private void assertRefreshedToken(IAuthenticationResult result, String expectedT assertEquals(expectedToken, result.accessToken()); assertEquals(expectedReason, result.metadata().cacheRefreshReason()); } - - String readResource(String resource) { - try { - return new String(Files.readAllBytes(Paths.get(getClass().getResource(resource).toURI()))); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } - } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java index 326e933f..3af3e794 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java @@ -19,31 +19,31 @@ class AuthorityTest { @Test void testDetectAuthorityType_AAD() throws Exception { URL url = new URL(TestConfiguration.AAD_TENANT_ENDPOINT); - assertEquals(Authority.detectAuthorityType(url), AuthorityType.AAD); + assertEquals(AuthorityType.AAD, Authority.detectAuthorityType(url)); } @Test void testDetectAuthorityType_ADFS() throws Exception { URL url = new URL(TestConfiguration.ADFS_TENANT_ENDPOINT); - assertEquals(Authority.detectAuthorityType(url), AuthorityType.ADFS); + assertEquals(AuthorityType.ADFS, Authority.detectAuthorityType(url)); } @Test void testDetectAuthorityType_B2C() throws Exception { URL url = new URL(TestConfiguration.B2C_AUTHORITY); - assertEquals(Authority.detectAuthorityType(url), AuthorityType.B2C); + assertEquals(AuthorityType.B2C, Authority.detectAuthorityType(url)); } @ParameterizedTest @MethodSource("com.microsoft.aad.msal4j.AuthorityTest#ciamAuthorities") void testDetectAuthorityType_CIAM(URL authority) throws Exception { - assertEquals(Authority.detectAuthorityType(authority), AuthorityType.CIAM); + assertEquals(AuthorityType.CIAM, Authority.detectAuthorityType(authority)); } @ParameterizedTest @MethodSource("com.microsoft.aad.msal4j.AuthorityTest#validCiamAuthoritiesAndTransformedAuthority") void testCiamAuthorityTransformation(URL authority, URL transformedAuthority) throws Exception { - assertEquals(CIAMAuthority.transformAuthority(authority), transformedAuthority); + assertEquals(transformedAuthority, CIAMAuthority.transformAuthority(authority)); } @Test @@ -100,35 +100,35 @@ void testValidateAuthorityEmptyPath() { void testConstructor_AADAuthority() throws MalformedURLException { final AADAuthority aa = new AADAuthority(new URL(TestConfiguration.AAD_TENANT_ENDPOINT)); assertNotNull(aa); - assertEquals(aa.authority(), - TestConfiguration.AAD_TENANT_ENDPOINT); - assertEquals(aa.host(), TestConfiguration.AAD_HOST_NAME); - assertEquals(aa.tokenEndpoint(), - TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - assertEquals(aa.selfSignedJwtAudience(), - TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - assertEquals(aa.tokenEndpoint(), - TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - assertEquals(aa.authorityType(), AuthorityType.AAD); + assertEquals(TestConfiguration.AAD_TENANT_ENDPOINT, + aa.authority()); + assertEquals(TestConfiguration.AAD_HOST_NAME, aa.host()); + assertEquals(TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token", + aa.tokenEndpoint()); + assertEquals(TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token", + aa.selfSignedJwtAudience()); + assertEquals(TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token", + aa.tokenEndpoint()); + assertEquals(AuthorityType.AAD, aa.authorityType()); assertFalse(aa.isTenantless()); - assertEquals(aa.deviceCodeEndpoint(), - TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/devicecode"); + assertEquals(TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/devicecode", + aa.deviceCodeEndpoint()); } @Test void testConstructor_B2CAuthority() throws MalformedURLException { final B2CAuthority aa = new B2CAuthority(new URL(TestConfiguration.B2C_AUTHORITY)); assertNotNull(aa); - assertEquals(aa.authority(), - TestConfiguration.B2C_AUTHORITY + "/"); - assertEquals(aa.host(), TestConfiguration.B2C_HOST_NAME); - assertEquals(aa.selfSignedJwtAudience(), - TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); - assertEquals(aa.tokenEndpoint(), - TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); - assertEquals(aa.authorityType(), AuthorityType.B2C); - assertEquals(aa.tokenEndpoint(), - TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); + assertEquals(TestConfiguration.B2C_AUTHORITY + "/", + aa.authority()); + assertEquals(TestConfiguration.B2C_HOST_NAME, aa.host()); + assertEquals(TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY, + aa.selfSignedJwtAudience()); + assertEquals(TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY, + aa.tokenEndpoint()); + assertEquals(AuthorityType.B2C, aa.authorityType()); + assertEquals(TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY, + aa.tokenEndpoint()); assertFalse(aa.isTenantless()); } @@ -136,15 +136,15 @@ void testConstructor_B2CAuthority() throws MalformedURLException { void testConstructor_ADFSAuthority() throws MalformedURLException { final ADFSAuthority a = new ADFSAuthority(new URL(TestConfiguration.ADFS_TENANT_ENDPOINT)); assertNotNull(a); - assertEquals(a.authority(), TestConfiguration.ADFS_TENANT_ENDPOINT); - assertEquals(a.host(), TestConfiguration.ADFS_HOST_NAME); - assertEquals(a.selfSignedJwtAudience(), - TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT); + assertEquals(TestConfiguration.ADFS_TENANT_ENDPOINT, a.authority()); + assertEquals(TestConfiguration.ADFS_HOST_NAME, a.host()); + assertEquals(TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT, + a.selfSignedJwtAudience()); - assertEquals(a.authorityType(), AuthorityType.ADFS); + assertEquals(AuthorityType.ADFS, a.authorityType()); - assertEquals(a.tokenEndpoint(), - TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT); + assertEquals(TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT, + a.tokenEndpoint()); assertFalse(a.isTenantless()); } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java index 034ffc0e..a200a24a 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java @@ -35,10 +35,10 @@ void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingException { .extraQueryParameters(extraParameters) .build(); - assertEquals(parameters.responseMode(), ResponseMode.FORM_POST); - assertEquals(parameters.redirectUri(), redirectUri); - assertEquals(parameters.scopes().size(), 4); - assertEquals(parameters.extraQueryParameters.size(), 2); + assertEquals(ResponseMode.FORM_POST, parameters.responseMode()); + assertEquals(redirectUri, parameters.redirectUri()); + assertEquals(4, parameters.scopes().size()); + assertEquals(2, parameters.extraQueryParameters.size()); assertNull(parameters.loginHint()); assertNull(parameters.codeChallenge()); @@ -50,8 +50,8 @@ void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingException { URL authorizationUrl = app.getAuthorizationRequestUrl(parameters); - assertEquals(authorizationUrl.getHost(), "login.microsoftonline.com"); - assertEquals(authorizationUrl.getPath(), "/common/oauth2/v2.0/authorize"); + assertEquals("login.microsoftonline.com", authorizationUrl.getHost()); + assertEquals("/common/oauth2/v2.0/authorize", authorizationUrl.getPath()); Map queryParameters = new HashMap<>(); String query = authorizationUrl.getQuery(); @@ -64,12 +64,12 @@ void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingException { URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } - assertEquals(queryParameters.get("scope"), "openid profile offline_access scope"); - assertEquals(queryParameters.get("response_type"), "code"); - assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); - assertEquals(queryParameters.get("client_id"), "client_id"); - assertEquals(queryParameters.get("response_mode"), "form_post"); - assertEquals(queryParameters.get("id_token_hint"),"test"); + assertEquals("openid profile offline_access scope", queryParameters.get("scope")); + assertEquals("code", queryParameters.get("response_type")); + assertEquals("http://localhost:8080", queryParameters.get("redirect_uri")); + assertEquals("client_id", queryParameters.get("client_id")); + assertEquals("form_post", queryParameters.get("response_mode")); + assertEquals("test", queryParameters.get("id_token_hint")); } @Test @@ -110,9 +110,9 @@ void testBuilder_responseMode() throws UnsupportedEncodingException { .responseMode(ResponseMode.QUERY) // This should be overridden to FORM_POST .build(); - assertEquals(parameters.responseMode(), ResponseMode.FORM_POST); - assertEquals(parameters.redirectUri(), redirectUri); - assertEquals(parameters.scopes().size(), 4); + assertEquals(ResponseMode.FORM_POST, parameters.responseMode()); + assertEquals(redirectUri, parameters.redirectUri()); + assertEquals(4, parameters.scopes().size()); assertNull(parameters.loginHint()); assertNull(parameters.codeChallenge()); @@ -124,8 +124,8 @@ void testBuilder_responseMode() throws UnsupportedEncodingException { URL authorizationUrl = app.getAuthorizationRequestUrl(parameters); - assertEquals(authorizationUrl.getHost(), "login.microsoftonline.com"); - assertEquals(authorizationUrl.getPath(), "/common/oauth2/v2.0/authorize"); + assertEquals("login.microsoftonline.com", authorizationUrl.getHost()); + assertEquals("/common/oauth2/v2.0/authorize", authorizationUrl.getPath()); Map queryParameters = new HashMap<>(); String query = authorizationUrl.getQuery(); @@ -138,10 +138,10 @@ void testBuilder_responseMode() throws UnsupportedEncodingException { URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } - assertEquals(queryParameters.get("scope"), "openid profile offline_access scope"); - assertEquals(queryParameters.get("response_type"), "code"); - assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); - assertEquals(queryParameters.get("client_id"), "client_id"); - assertEquals(queryParameters.get("response_mode"), "form_post"); + assertEquals("openid profile offline_access scope", queryParameters.get("scope")); + assertEquals("code", queryParameters.get("response_type")); + assertEquals("http://localhost:8080", queryParameters.get("redirect_uri")); + assertEquals("client_id", queryParameters.get("client_id")); + assertEquals("form_post", queryParameters.get("response_mode")); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTest.java index a30a3951..35d2dbf4 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTest.java @@ -30,7 +30,7 @@ @ExtendWith(MockitoExtension.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class CacheFormatTests { +class CacheFormatTest { String TOKEN_RESPONSE = "/token_response.json"; String TOKEN_RESPONSE_ID_TOKEN = "/token_response_id_token.json"; diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTest.java index ca2cf4f2..11f0d1c0 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheTest.java @@ -21,7 +21,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class CacheTests { +class CacheTest { private TokenCache tokenCache; diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTest.java index 4b3cce13..7d682ba0 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DateTimeTest.java @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.*; -class DateTimeTests { +class DateTimeTest { @Test void parseUnixTimestampInSeconds() { diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTest.java similarity index 98% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTest.java index 08ca2e39..895570c8 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HelperAndUtilityTest.java @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; -class HelperAndUtilityTests { +class HelperAndUtilityTest { @Test void StringHelper_serializeQueryParameters_ValidUrlQueryStrings() { @@ -78,7 +78,7 @@ void StringHelper_convertToMultiValueMap() { assertNotNull(convertedInternalMap, "Converted map should not be null"); assertNotNull(methodReturnedMap, "Method returned map should not be null"); - assertEquals(convertedInternalMap.size(), methodReturnedMap.size(), "Maps should have the same size"); + assertEquals(methodReturnedMap.size(), convertedInternalMap.size(), "Maps should have the same size"); for (String key : convertedInternalMap.keySet()) { assertTrue(methodReturnedMap.containsKey(key), "Method returned map should contain key: " + key); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java index 60ae060f..3c570659 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java @@ -40,12 +40,12 @@ void testHttpHeaderConstructor() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), "app-name"); - assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); - assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); + assertEquals(HttpHeaders.PRODUCT_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME)); + assertEquals(HttpHeaders.PRODUCT_VERSION_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME)); + assertEquals(HttpHeaders.OS_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME)); + assertEquals("app-name", httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME)); + assertEquals("app-version", httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME)); + assertEquals("correlation-id", httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); } @Test @@ -66,9 +66,9 @@ void testHttpHeaderConstructor_valuesNotSet() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); + assertEquals(HttpHeaders.PRODUCT_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME)); + assertEquals(HttpHeaders.PRODUCT_VERSION_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME)); + assertEquals(HttpHeaders.OS_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME)); assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME)); assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME)); assertNotNull(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); @@ -97,7 +97,7 @@ void testHttpHeaderConstructor_userIdentifierUPN() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); String expectedValue = String.format(HttpHeaders.X_ANCHOR_MAILBOX_UPN_FORMAT, upn); - assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), expectedValue); + assertEquals(expectedValue, httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX)); } @Test @@ -122,7 +122,7 @@ void testHttpHeaderConstructor_userIdentifierHomeAccountId() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), "oid:userObjectId@userTenantId"); + assertEquals("oid:userObjectId@userTenantId", httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX)); } @Test @@ -158,16 +158,16 @@ void testHttpHeaderConstructor_extraHttpHeadersOverwriteLibraryHeaders() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); // Standard headers - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); - assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); - assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); + assertEquals(HttpHeaders.PRODUCT_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME)); + assertEquals(HttpHeaders.PRODUCT_VERSION_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME)); + assertEquals(HttpHeaders.OS_HEADER_VALUE, httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME)); + assertEquals("app-version", httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME)); + assertEquals("correlation-id", httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); // Overwritten standard header - assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), uniqueAppName); + assertEquals(uniqueAppName, httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME)); // Extra header - assertEquals(httpHeaderMap.get(uniqueHeaderKey), uniqueHeaderValue); + assertEquals(uniqueHeaderValue, httpHeaderMap.get(uniqueHeaderKey)); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTest.java similarity index 98% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTest.java index cc15ee94..9c60f0ae 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/JsonCompatibilityTest.java @@ -12,14 +12,14 @@ //These tests were added to ensure the new usages of com.azure.json are functionally the same as the old usages of com.nimbusds packages. //Once we are confident in the new behavior these should no longer be necessary. -class JsonCompatibilityTests { +class JsonCompatibilityTest { //New style, using helper methods in JsonHelper that use com.azure.json private final Map newStyleParsedClaims = JsonHelper.parseJsonToMap(JsonHelper.getTokenPayloadClaims(TestHelper.ENCODED_JWT)); //Old style, using com.nimbusds methods private final Map oldStyleParsedClaims = JWTParser.parse(TestHelper.ENCODED_JWT).getJWTClaimsSet().getClaims(); - JsonCompatibilityTests() throws ParseException { + JsonCompatibilityTest() throws ParseException { } @Test diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTest.java index f5a8ccc0..d0197cb8 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ManagedIdentityTest.java @@ -37,7 +37,7 @@ @ExtendWith(MockitoExtension.class) @TestInstance(TestInstance.Lifecycle.PER_METHOD) -class ManagedIdentityTests { +class ManagedIdentityTest { private static final String EXPECTED_SKU = "MSAL.Java"; private static final String TEST_CORRELATION_ID = "00000000-0000-0000-0000-000000000001"; diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java index 131357ad..f988563c 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java @@ -48,8 +48,8 @@ void testMexParsingWs13() throws Exception { } BindingPolicy endpoint = MexParser.getWsTrustEndpointFromMexResponse(sb.toString(), false); - assertEquals(endpoint.getUrl(), - "https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed"); + assertEquals("https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed", + endpoint.getUrl()); } @Test @@ -69,7 +69,7 @@ void testMexParsingWs2005() throws Exception { } BindingPolicy endpoint = MexParser.getWsTrustEndpointFromMexResponse(sb .toString(), false); - assertEquals(endpoint.getUrl(), "https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed"); + assertEquals("https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed", endpoint.getUrl()); } @Test @@ -89,7 +89,7 @@ void testMexParsingIntegrated() throws Exception { } BindingPolicy endpoint = MexParser.getPolicyFromMexResponseForIntegrated(sb .toString(), false); - assertEquals(endpoint.getUrl(), - "https://msft.sts.microsoft.com/adfs/services/trust/13/windowstransport"); + assertEquals("https://msft.sts.microsoft.com/adfs/services/trust/13/windowstransport", + endpoint.getUrl()); } } \ No newline at end of file diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizationGrantTest.java similarity index 95% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizationGrantTest.java index a8d8f3de..278df934 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizationGrantTest.java @@ -15,7 +15,7 @@ import java.util.Map; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MsalOauthAuthorizatonGrantTest { +class MsalOauthAuthorizationGrantTest { @Test void testToParameters() { diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTest.java index 5aeb9f27..b48d0eb3 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OnBehalfOfTest.java @@ -18,7 +18,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class OnBehalfOfTests { +class OnBehalfOfTest { @Test void OnBehalfOf_InternalCacheLookup_Success() throws Exception { diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTest.java similarity index 99% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTest.java index 8f809962..f9d7a394 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTest.java @@ -20,7 +20,7 @@ import java.util.concurrent.Executors; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class ServerTelemetryTests { +class ServerTelemetryTest { private static final String SCHEMA_VERSION = "5"; private static final String CURRENT_REQUEST_HEADER_NAME = "x-client-current-telemetry"; diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTest.java similarity index 87% rename from msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java rename to msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTest.java index 69581641..ac3d6428 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTest.java @@ -21,7 +21,7 @@ import java.util.function.Consumer; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class TelemetryTests { +class TelemetryTest { private List> eventsReceived = new ArrayList<>(); private String tenantId = "tenantId123"; @@ -32,11 +32,6 @@ private class MyTelemetryConsumer { Consumer>> telemetryConsumer = (List> telemetryEvents) -> { eventsReceived.addAll(telemetryEvents); - System.out.println("Received " + telemetryEvents.size() + " events"); - telemetryEvents.forEach(event -> { - System.out.print("Event Name: " + event.get("event_name")); - event.entrySet().forEach(entry -> System.out.println(" " + entry)); - }); }; } @@ -74,7 +69,7 @@ void telemetryManagerFlush_EventCountTest() { telemetryManager.flush(reqId, clientId); // 1 Default event, 1 API event, 1 Http event - assertEquals(eventsReceived.size(), 3); + assertEquals(3, eventsReceived.size()); } @Test @@ -99,7 +94,7 @@ void onSendFailureTrue_SkipEventsIfSuccessfulTest() { telemetryManager.flush(reqId, clientId); // API event was successful, so count should be 0 - assertEquals(eventsReceived.size(), 0); + assertEquals(0, eventsReceived.size()); eventsReceived.clear(); String reqId2 = telemetryManager.generateRequestId(); @@ -116,19 +111,19 @@ void onSendFailureTrue_SkipEventsIfSuccessfulTest() { telemetryManager.flush(reqId2, clientId); // API event failed, so count should be 3 (1 default, 1 Api, 1 http) - assertEquals(eventsReceived.size(), 3); + assertEquals(3, eventsReceived.size()); } @Test void telemetryInternalApi_ScrubTenantFromUriTest() throws Exception { - assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common/oauth2/v2.0/token")), - "https://login.microsoftonline.com//oauth2/v2.0/token"); + assertEquals("https://login.microsoftonline.com//oauth2/v2.0/token", + Event.scrubTenant(new URI("https://login.microsoftonline.com/common/oauth2/v2.0/token"))); - assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common")), - "https://login.microsoftonline.com/"); + assertEquals("https://login.microsoftonline.com/", + Event.scrubTenant(new URI("https://login.microsoftonline.com/common"))); - assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth")), - "https://login.microsoftonline.com/tfp//B2C_1_ROPC_Auth"); + assertEquals("https://login.microsoftonline.com/tfp//B2C_1_ROPC_Auth", + Event.scrubTenant(new URI("https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth"))); assertNull(Event.scrubTenant(new URI("https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth"))); @@ -155,7 +150,7 @@ void telemetryContainsDefaultEventTest() { telemetryManager.flush(reqId, clientId); - assertEquals(eventsReceived.get(0).get("event_name"), "msal.default_event"); + assertEquals("msal.default_event", eventsReceived.get(0).get("event_name")); } @Test @@ -177,7 +172,7 @@ void telemetryFlushEventWithoutStopping_OrphanedEventIncludedTest() { telemetryManager.stopEvent(reqId, apiEvent1); telemetryManager.flush(reqId, clientId); - assertEquals(eventsReceived.size(), 3); + assertEquals(3, eventsReceived.size()); assertTrue(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); } @@ -202,7 +197,7 @@ void telemetryStopEventWithoutStarting_NoExceptionThrownTest() { telemetryManager.flush(reqId, clientId); - assertEquals(eventsReceived.size(), 2); + assertEquals(2, eventsReceived.size()); assertFalse(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); } @@ -222,7 +217,7 @@ void piiLoggingEnabled_ApiEventHashTest() { telemetryManager.stopEvent(reqId, apiEvent); assertNotNull(apiEvent.get("msal.tenant_id")); - assertNotEquals(apiEvent.get("msal.tenant_id"), tenantId); + assertNotEquals(tenantId, apiEvent.get("msal.tenant_id")); } @Test @@ -256,7 +251,7 @@ void authorityNotInTrustedHostList_AuthorityIsNullTest() throws URISyntaxExcepti apiEvent.setWasSuccessful(true); telemetryManager.stopEvent(reqId, apiEvent); - assertEquals(apiEvent.get("msal.authority"), "https://login.microsoftonline.com"); + assertEquals("https://login.microsoftonline.com", apiEvent.get("msal.authority")); ApiEvent apiEvent2 = new ApiEvent(false); @@ -273,10 +268,10 @@ void xmsCliTelemetryTest_CorrectFormatTest() { String responseHeader = "1,0,0,,"; XmsClientTelemetryInfo info = XmsClientTelemetryInfo.parseXmsTelemetryInfo(responseHeader); - assertEquals(info.getServerErrorCode(), "0"); - assertEquals(info.getServerSubErrorCode(), "0"); - assertEquals(info.getTokenAge(), ""); - assertEquals(info.getSpeInfo(), ""); + assertEquals("0", info.getServerErrorCode()); + assertEquals("0", info.getServerSubErrorCode()); + assertEquals("", info.getTokenAge()); + assertEquals("", info.getSpeInfo()); } @Test diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java index ac743755..4742459c 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java @@ -65,7 +65,7 @@ void executeOAuthRequest_SCBadRequestErrorInvalidGrant_InteractionRequiredExcept fail("Expected MsalServiceException was not thrown"); } catch (MsalInteractionRequiredException ex) { assertEquals(claims.replace("\\", ""), ex.claims()); - assertEquals(ex.reason(), InteractionRequiredExceptionReason.BASIC_ACTION); + assertEquals(InteractionRequiredExceptionReason.BASIC_ACTION, ex.reason()); } } @@ -239,7 +239,7 @@ void testExecuteOAuth_Success() throws MsalException, IOException, URISyntaxExce assertNotNull(result.account()); assertNotNull(result.account().homeAccountId()); - assertEquals(result.account().username(), "idlab@msidlab4.onmicrosoft.com"); + assertEquals("idlab@msidlab4.onmicrosoft.com", result.account().username()); assertFalse(StringHelper.isBlank(result.accessToken())); assertFalse(StringHelper.isBlank(result.refreshToken())); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java index dd020db8..cccd0d47 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java @@ -50,7 +50,7 @@ void escapeXMLElementDataTest() { String DATA_TO_ESCAPE = "o_!as & a34~'fe<> \" a1"; String XML_ESCAPED_DATA = "o_!as & a34~'fe<> " a1"; - assertEquals(WSTrustRequest.escapeXMLElementData(DATA_TO_ESCAPE), XML_ESCAPED_DATA); + assertEquals(XML_ESCAPED_DATA, WSTrustRequest.escapeXMLElementData(DATA_TO_ESCAPE)); assertEquals(WSTrustRequest.escapeXMLElementData(DATA_TO_ESCAPE), StringEscapeUtils.escapeXml10(DATA_TO_ESCAPE)); }