//  Copyright (c) Microsoft Corporation.
//  All rights reserved.
//
//  This code is licensed under the MIT License.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files(the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions :
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

package com.microsoft.aad.adal;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorDescription;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Base64;

import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
import com.microsoft.identity.common.adal.internal.net.HttpUrlConnectionFactory;
import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectory;
import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectoryCloud;

import org.json.JSONException;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import static com.microsoft.aad.adal.AuthenticationConstants.Broker.BROKER_ACCOUNT_TYPE;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.TOKEN_BROKER_RESPONSE;
import static com.microsoft.identity.common.java.AuthenticationConstants.UIRequest.BROWSER_FLOW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Tests for {@link AcquireTokenRequest}.
 */
@RunWith(AndroidJUnit4.class)
@Ignore
public final class AcquireTokenRequestTest {

    private static final String TAG = AcquireTokenRequestTest.class.getSimpleName();
    /**
     * Check case-insensitive lookup
     */
    private static final String VALID_AUTHORITY = "https://login.windows.net/test.onmicrosoft.com";
    private static final int ACTIVITY_TIME_OUT = 1000;
    private static final int MINUS_MINUTE = 10;
    private static final int EXTEND_MINUS_MINUTE = 60;
    private static final String TEST_UPN = "testupn";
    private static final String TEST_USERID = "testuserid";
    private static final int ACCOUNT_MANAGER_ERROR_CODE_BAD_AUTHENTICATION = 9;
    private static final int MAX_RESILIENCY_ERROR_CODE = 599;

    @Before
    public void setUp() throws Exception {
        Logger.d(TAG, "setup key at settings");
        System.setProperty(
                "dexmaker.dexcache",
                androidx.test.platform.app.InstrumentationRegistry
                        .getInstrumentation()
                        .getTargetContext()
                        .getCacheDir()
                        .getPath()
        );

        System.setProperty(
                "org.mockito.android.target",
                ApplicationProvider
                        .getApplicationContext()
                        .getCacheDir()
                        .getPath()
        );

        if (AuthenticationSettings.INSTANCE.getSecretKeyData() == null) {
            // use same key for tests
            final SecretKeyFactory keyFactory =
                    SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
            final int iterationCount = 100;
            final int keyLength = 256;

            final SecretKey tempkey = keyFactory.generateSecret(
                    new PBEKeySpec("test".toCharArray(),
                            "abcdedfdfd".getBytes(StandardCharsets.UTF_8),
                            iterationCount,
                            keyLength
                    )
            );

            final SecretKey secretKey = new SecretKeySpec(tempkey.getEncoded(), "AES");
            AuthenticationSettings.INSTANCE.setSecretKey(secretKey.getEncoded());
        }

        final InstanceDiscoveryMetadata metadata = new InstanceDiscoveryMetadata(
                "login.microsoftonline.com",
                "login.windows.net"
        );

        final AzureActiveDirectoryCloud cloud = CoreAdapter.asAadCloud(metadata);

        AuthorityValidationMetadataCache.updateInstanceDiscoveryMap(
                "login.windows.net",
                metadata
        );

        AzureActiveDirectory.putCloud("login.windows.net", cloud);
    }

    @After
    public void tearDown() {
        HttpUrlConnectionFactory.setMockedHttpUrlConnection(null);
        Logger.getInstance().setExternalLogger(null);
        AuthenticationSettings.INSTANCE.setUseBroker(false);
    }

    /**
     * Test if there is a valid AT in local cache, will use it even we can switch to broker for auth.
     */
    @Test
    public void testFavorLocalCacheValidATInLocalCache()
            throws PackageManager.NameNotFoundException, OperationCanceledException, IOException,
            AuthenticatorException, InterruptedException {

        // Make sure AT is not expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(MINUS_MINUTE),
                false, // store mrrt
                false, // store frt
                null // ext expires
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false,
                cacheStore
        );

        final TestAuthCallback callback = new TestAuthCallback();
        authContext.acquireTokenSilentAsync(
                "resource", // resource
                "clientid", // client id
                TEST_USERID,
                callback
        );

        final CountDownLatch signal = new CountDownLatch(1);
        signal.await(ACTIVITY_TIME_OUT, TimeUnit.MILLISECONDS);

        assertNull(callback.getCallbackException());
        assertNotNull(callback.getCallbackResult());
        assertTrue(callback.getCallbackResult().getAccessToken().equals("I am an AT"));

        cacheStore.removeAll();
    }

    /**
     * Test if there is a RT in cache, we'll use the RT first. If it fails and if we can switch
     * to broker for auth, will switch to broker.
     */
    @Test
    public void testFavorLocalCacheUseLocalRTFailsSwitchToBroker()
            throws PackageManager.NameNotFoundException, OperationCanceledException, IOException,
            AuthenticatorException, InterruptedException, JSONException {
        // Make sure AT is expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(-MINUS_MINUTE),
                true, // store mrrt
                true, // store frt
                null // ext expires
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);
        mockGetAuthTokenCall(mockedAccountManager, true);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        final String errorCode = "invalid_request";
        prepareFailedHttpUrlConnection(errorCode, errorCode);

        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false, // validate authority
                cacheStore
        );

        final TestAuthCallback callback = new TestAuthCallback();

        authContext.acquireTokenSilentAsync(
                "resource",
                "clientid",
                TEST_USERID,
                callback
        );

        final CountDownLatch signal = new CountDownLatch(1);
        signal.await(ACTIVITY_TIME_OUT, TimeUnit.MILLISECONDS);

        // verify getAuthToken called once
        verify(
                mockedAccountManager,
                times(1)
        ).getAuthToken(
                Mockito.any(Account.class),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(Bundle.class),
                ArgumentMatchers.eq(false),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        // verify returned AT is as expected
        assertNull(callback.getCallbackException());
        assertNotNull(callback.getCallbackResult());
        assertEquals(
                "I am an access token from broker",
                callback.getCallbackResult().getAccessToken()
        );

        //verify local cache is not cleared
        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForRTEntry(
                                VALID_AUTHORITY,
                                "resource", // resource
                                "clientid", // client id
                                TEST_USERID
                        )
                )
        );

        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForRTEntry(
                                VALID_AUTHORITY,
                                "resource",
                                "clientid",
                                TEST_UPN
                        )
                )
        );

        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForMRRT(
                                VALID_AUTHORITY,
                                "clientid",
                                TEST_UPN
                        )
                )
        );

        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForMRRT(
                                VALID_AUTHORITY,
                                "clientid",
                                TEST_USERID
                        )
                )
        );

        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForFRT(
                                VALID_AUTHORITY,
                                AuthenticationConstants.MS_FAMILY_ID,
                                TEST_UPN
                        )
                )
        );

        assertNull(
                cacheStore.getItem(
                        CacheKey.createCacheKeyForFRT(
                                VALID_AUTHORITY,
                                AuthenticationConstants.MS_FAMILY_ID,
                                TEST_USERID
                        )
                )
        );

        assertFalse(authContext.getCache().getAll().hasNext());
        cacheStore.removeAll();
    }

    /**
     * Test if there is a RT in cache, we'll use the RT first. If it fails with invalid_grant,
     * local cache will be cleared. If we can switch to broker, will switch to broker for auth.
     */
    @Test
    public void testFavorLocalCacheUseLocalRTFailsWithInvalidGrantSwitchToBroker()
            throws PackageManager.NameNotFoundException, OperationCanceledException,
            IOException, AuthenticatorException, InterruptedException, JSONException {

        // Make sure AT is expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(-MINUS_MINUTE),
                true, // store mrt
                false, // store frt
                null // ext expires
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);
        mockGetAuthTokenCall(mockedAccountManager, true);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        prepareFailedHttpUrlConnection("invalid_grant");
        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false, // validate authority
                cacheStore
        );

        final CountDownLatch signal = new CountDownLatch(1);
        authContext.acquireTokenSilentAsync(
                "resource",
                "clientid",
                TEST_USERID,
                new AuthenticationCallback<AuthenticationResult>() {
                    @Override
                    public void onSuccess(final AuthenticationResult result) {
                        // verify getAuthToken called once
                        verify(
                                mockedAccountManager,
                                times(1)
                        ).getAuthToken(
                                Mockito.any(Account.class),
                                ArgumentMatchers.anyString(),
                                ArgumentMatchers.any(Bundle.class),
                                ArgumentMatchers.eq(false),
                                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                                ArgumentMatchers.any(Handler.class)
                        );

                        assertNotNull(result);

                        assertEquals(
                                "I am an access token from broker",
                                result.getAccessToken()
                        );

                        //verify local cache is cleared
                        assertNull(
                                cacheStore.getItem(
                                        CacheKey.createCacheKeyForRTEntry(
                                                VALID_AUTHORITY,
                                                "resource",
                                                "clientid", TEST_USERID
                                        )
                                )
                        );

                        assertNull(
                                cacheStore.getItem(
                                        CacheKey.createCacheKeyForRTEntry(
                                                VALID_AUTHORITY,
                                                "resource",
                                                "clientid",
                                                TEST_UPN
                                        )
                                )
                        );

                        assertNull(
                                cacheStore.getItem(
                                        CacheKey.createCacheKeyForMRRT(
                                                VALID_AUTHORITY,
                                                "clientid",
                                                TEST_UPN
                                        )
                                )
                        );

                        assertNull(
                                cacheStore.getItem(
                                        CacheKey.createCacheKeyForMRRT(
                                                VALID_AUTHORITY,
                                                "clientid",
                                                TEST_USERID
                                        )
                                )
                        );

                        assertFalse(authContext.getCache().getAll().hasNext());
                        cacheStore.removeAll();
                        signal.countDown();
                    }

                    @Override
                    public void onError(final Exception exc) {
                        fail();
                    }
                }
        );

        signal.await();
    }

    /**
     * Test if there is a RT in cache, we'll use the RT first. If it fails and if we can switch to broker for auth,
     * will switch to broker.
     */
    @Test
    public void testFavorLocalCacheUseLocalRTSucceeds()
            throws PackageManager.NameNotFoundException, OperationCanceledException,
            IOException, AuthenticatorException, InterruptedException, JSONException {
        // Make sure AT is expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(-MINUS_MINUTE),
                false, // store mrrt
                false, // store frt
                null // ext expires
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);
        mockGetAuthTokenCall(mockedAccountManager, true);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        //mock HttpUrlConnection for refresh token request
        prepareSuccessHttpUrlConnection();

        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false, // validate authority
                cacheStore
        );

        final TestAuthCallback callback = new TestAuthCallback();

        authContext.acquireTokenSilentAsync(
                "resource",
                "clientid",
                TEST_USERID,
                callback
        );

        final CountDownLatch signal = new CountDownLatch(1);
        signal.await(ACTIVITY_TIME_OUT, TimeUnit.MILLISECONDS);

        // verify getAuthToken not called
        verify(
                mockedAccountManager,
                times(0)
        ).getAuthToken(
                Mockito.any(Account.class),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(Bundle.class),
                ArgumentMatchers.eq(false),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        // verify returned AT is as expected
        assertNull(callback.getCallbackException());
        assertNotNull(callback.getCallbackResult());
        assertEquals(
                "I am a new access token",
                callback.getCallbackResult().getAccessToken()
        );

        assertTrue(cacheStore.getAll().hasNext());
        cacheStore.removeAll();
    }

    @Test
    public void testBothLocalAndBrokerSilentAuthFailedSwitchedToBrokerForInteractive()
            throws OperationCanceledException, IOException, AuthenticatorException,
            PackageManager.NameNotFoundException, InterruptedException, JSONException {

        // Make sure AT is expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(-MINUS_MINUTE),
                false,
                false,
                null
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);
        mockGetAuthTokenCallWithNoAccountFound(mockedAccountManager);
        mockAddAccountCall(mockedAccountManager);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        prepareFailedHttpUrlConnection("invalid_request");
        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false, // validate authority
                cacheStore
        );

        final TestAuthCallback callback = new TestAuthCallback();

        final CountDownLatch latch = new CountDownLatch(1);
        final Activity mockedActivity = Mockito.mock(Activity.class);

        doAnswer(new Answer() {
                     @Override
                     public Void answer(final InvocationOnMock invocation) {
                         latch.countDown();
                         return null;
                     }
                 }
        ).when(mockedActivity).startActivityForResult(Mockito.any(Intent.class), Mockito.anyInt());

        authContext.acquireToken(
                mockedActivity,
                "resource",
                "clientid",
                authContext.getRedirectUriForBroker(),
                TEST_UPN,
                callback
        );

        latch.await();

        // verify getAuthToken called once
        verify(
                mockedAccountManager,
                times(1)
        ).getAuthToken(
                Mockito.any(Account.class),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(Bundle.class),
                ArgumentMatchers.eq(false),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        verify(
                mockedAccountManager,
                times(1)
        ).addAccount(
                ArgumentMatchers.refEq(BROKER_ACCOUNT_TYPE),
                anyString(),
                (String[]) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Bundle.class),
                (Activity) ArgumentMatchers.eq(null),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        assertFalse(cacheStore.getAll().hasNext());
        cacheStore.removeAll();
    }

    @Test
    public void testLocalSilentFailedBrokerSilentReturnErrorCannotTryWithInteractive()
            throws OperationCanceledException, IOException, AuthenticatorException,
            PackageManager.NameNotFoundException, InterruptedException, JSONException {
        // Make sure AT is expired
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(-MINUS_MINUTE),
                false,
                false,
                null
        );

        final AccountManager mockedAccountManager = getMockedAccountManager();
        mockAccountManagerGetAccountBehavior(mockedAccountManager);
        mockGetAuthTokenCall(mockedAccountManager, false);
        mockAddAccountCall(mockedAccountManager);

        final FileMockContext mockContext = createMockContext();
        mockContext.setMockedAccountManager(mockedAccountManager);

        prepareFailedHttpUrlConnection("invalid_request");
        prepareAuthForBrokerCall();

        final AuthenticationContext authContext = new AuthenticationContext(mockContext,
                VALID_AUTHORITY, false, cacheStore);
        final TestAuthCallback callback = new TestAuthCallback();

        authContext.acquireToken(
                Mockito.mock(Activity.class),
                "resource",
                "clientid",
                authContext.getRedirectUriForBroker(),
                TEST_UPN,
                callback
        );

        final CountDownLatch signal = new CountDownLatch(1);
        signal.await(ACTIVITY_TIME_OUT, TimeUnit.MILLISECONDS);

        // verify getAuthToken called once
        verify(
                mockedAccountManager,
                times(1)
        ).getAuthToken(
                Mockito.any(Account.class),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(Bundle.class),
                ArgumentMatchers.eq(false),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        verify(
                mockedAccountManager,
                times(0)
        ).addAccount(
                ArgumentMatchers.refEq(BROKER_ACCOUNT_TYPE),
                anyString(),
                (String[]) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Bundle.class),
                (Activity) ArgumentMatchers.eq(null),
                (AccountManagerCallback<Bundle>) ArgumentMatchers.eq(null),
                ArgumentMatchers.any(Handler.class)
        );

        assertFalse(cacheStore.getAll().hasNext());
        cacheStore.removeAll();
    }

    @Test
    public void testMultipleATExistForSameClientAppAndResource()
            throws PackageManager.NameNotFoundException, InterruptedException {
        // insert multiple ATs for the same client app and resource
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(MINUS_MINUTE),
                false,
                false,
                null
        );

        final String resource = "resource";
        final String clientId = "clientid";
        insertTokenForDifferentUser(clientId, resource, getExpireDate(MINUS_MINUTE), cacheStore);

        final FileMockContext mockContext = createMockContext();
        final AuthenticationContext authContext = new AuthenticationContext(mockContext,
                VALID_AUTHORITY, false, cacheStore);

        final CountDownLatch latch = new CountDownLatch(1);
        authContext.acquireTokenSilentAsync(
                resource,
                clientId,
                null, // user id
                new AuthenticationCallback<AuthenticationResult>() {
                    @Override
                    public void onSuccess(final AuthenticationResult result) {
                        fail("unexpected success");
                    }

                    @Override
                    public void onError(final Exception exc) {
                        assertTrue(exc instanceof AuthenticationException);
                        final AuthenticationException authenticationException = (AuthenticationException) exc;

                        assertEquals(
                                authenticationException.getCode(),
                                ADALError.AUTH_FAILED_USER_MISMATCH
                        );

                        latch.countDown();
                    }
                });
        latch.await();
    }

    @Test
    public void testMutipleMRRTExistForTheSameApp()
            throws PackageManager.NameNotFoundException, InterruptedException {
        final ITokenCacheStore cacheStore = getTokenCache(
                getExpireDate(MINUS_MINUTE),
                true,
                false,
                null
        );

        final String resource = "resource";
        final String clientId = "clientid";

        insertTokenForDifferentUser(clientId, resource, getExpireDate(MINUS_MINUTE), cacheStore);

        final FileMockContext mockContext = createMockContext();
        final AuthenticationContext authContext = new AuthenticationContext(
                mockContext,
                VALID_AUTHORITY,
                false, // validate authority
                cacheStore
        );

        final CountDownLatch latch = new CountDownLatch(1);
        authContext.acquireTokenSilentAsync(
                "different resource", // resource
                clientId,
                null, // user id
                new AuthenticationCallback<AuthenticationResult>() {
                    @Override
                    public void onSuccess(final AuthenticationResult result) {
                        fail("unexpected success");
                    }

                    @Override
                    public void onError(final Exception exc) {
                        assertTrue(exc instanceof AuthenticationException);
                        final AuthenticationException authenticationException = (AuthenticationException) exc;

                        assertEquals(
                                authenticationException.getCode(),
                                ADALError.AUTH_FAILED_USER_MISMATCH
                        );

                        latch.countDown();
                    }
                });
        latch.await();
    }

    private void insertTokenForDifferentUser(final String clientId,
                                             final String resource,
                                             final Date expiresOn,
                                             final ITokenCacheStore cacheStore) {
        final String anotherUpn = "another_upn";
        final String anotherUserId = "another_userid";
        final UserInfo userInfo = new UserInfo();
        userInfo.setDisplayableId(anotherUpn);
        userInfo.setUserId(anotherUserId);
        final String idToken = "I am a different id token";

        final AuthenticationResult result = new AuthenticationResult(
                "different at", // access token
                "different rt", // refresh token
                expiresOn,
                false, // is broad
                userInfo,
                "", // tenant id
                idToken,
                null, // ext expires
                clientId
        );

        final TokenCacheItem differentTokenItem = TokenCacheItem.createRegularTokenCacheItem(
                VALID_AUTHORITY,
                resource,
                clientId,
                result
        );

        cacheStore.setItem(
                CacheKey.createCacheKeyForRTEntry(
                        VALID_AUTHORITY,
                        resource,
                        clientId,
                        anotherUserId
                ), differentTokenItem
        );

        cacheStore.setItem(
                CacheKey.createCacheKeyForRTEntry(
                        VALID_AUTHORITY,
                        resource,
                        clientId,
                        anotherUpn
                ), differentTokenItem
        );

        cacheStore.setItem(
                CacheKey.createCacheKeyForRTEntry(
                        VALID_AUTHORITY,
                        resource,
                        clientId,
                        null
                ), differentTokenItem
        );

        final TokenCacheItem mrrtItem = TokenCacheItem.createMRRTTokenCacheItem(
                VALID_AUTHORITY,
                clientId,
                result
        );

