// 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 static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.BROWSER_CODE_AUTHENTICATION_EXCEPTION;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.BROWSER_CODE_CANCEL;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.BROWSER_CODE_COMPLETE;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.BROWSER_CODE_ERROR;
import static com.microsoft.aad.adal.AuthenticationConstants.UIResponse.TOKEN_BROKER_RESPONSE;
import static com.microsoft.identity.common.java.AuthenticationConstants.UIRequest.BROWSER_FLOW;
import static com.microsoft.identity.common.java.cache.SharedPreferencesAccountCredentialCache.DEFAULT_ACCOUNT_CREDENTIAL_SHARED_PREFERENCES;
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.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
import android.util.Base64;

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

import com.google.gson.Gson;
import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
import com.microsoft.identity.common.adal.internal.net.HttpUrlConnectionFactory;
import com.microsoft.identity.common.components.AndroidPlatformComponentsFactory;
import com.microsoft.identity.common.internal.broker.PackageHelper;
import com.microsoft.identity.common.java.cache.CacheKeyValueDelegate;
import com.microsoft.identity.common.java.cache.IAccountCredentialCache;
import com.microsoft.identity.common.java.cache.SharedPreferencesAccountCredentialCache;
import com.microsoft.identity.common.java.dto.AccountRecord;
import com.microsoft.identity.common.java.interfaces.IPlatformComponents;
import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectory;
import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectoryCloud;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;

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

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

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

@RunWith(AndroidJUnit4.class)
public final class AuthenticationContextTest {

    /**
     * Check case-insensitive lookup.
     */
    private static final String VALID_AUTHORITY = "https://login.windows.net/test.onmicrosoft.com";

    /**
     * This value Controls the timeout for CloudDownLatches used in various tests
     * You may want to increase this value when debugging a test
     */
    protected static final int CONTEXT_REQUEST_TIME_OUT = 20000;

    protected static final int ACTIVITY_TIME_OUT = 1000;

    private static final String TEST_AUTHORITY = "https://login.windows.net/ComMon/";

    private static final String TEST_PACKAGE_NAME = "com.microsoft.aad.adal.test";

    private static final int EXPIRES_ON_ADJUST_MINS = 10;

    static final String TEST_IDTOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiJlNzBiMTE1ZS1hYzBhLTQ4MjMtODVkYS04ZjRiN2I0ZjAwZTYiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8zMGJhYTY2Ni04ZGY4LTQ4ZTctOTdlNi03N2NmZDA5OTU5NjMvIiwibmJmIjoxMzc2NDI4MzEwLCJleHAiOjEzNzY0NTcxMTAsInZlciI6IjEuMCIsInRpZCI6IjMwYmFhNjY2LThkZjgtNDhlNy05N2U2LTc3Y2ZkMDk5NTk2MyIsIm9pZCI6IjRmODU5OTg5LWEyZmYtNDExZS05MDQ4LWMzMjIyNDdhYzYyYyIsInVwbiI6ImFkbWluQGFhbHRlc3RzLm9ubWljcm9zb2Z0LmNvbSIsInVuaXF1ZV9uYW1lIjoiYWRtaW5AYWFsdGVzdHMub25taWNyb3NvZnQuY29tIiwic3ViIjoiVDU0V2hGR1RnbEJMN1VWYWtlODc5UkdhZEVOaUh5LXNjenNYTmFxRF9jNCIsImZhbWlseV9uYW1lIjoiU2VwZWhyaSIsImdpdmVuX25hbWUiOiJBZnNoaW4ifQ.";

    static final String TEST_ACCESS_TOKEN_WITH_DEVICE_ID = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyIsImtpZCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLndpbmRvd3MubmV0IiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZjY0NWFkOTItZTM4ZC00ZDFhLWI1MTAtZDFiMDlhNzRhOGNhLyIsImlhdCI6MTY3MzgzNTUyNywibmJmIjoxNjczODM1NTI3LCJleHAiOjE2NzM4NDAwOTMsImFjciI6IjEiLCJhaW8iOiJBVFFBeS84VEFBQUFqTlBGYnQyakZHdmJMOGw2dUMvdG5lYUEvL0FKQWJTazhOS1NiVWJ5WWt0bTZmSHQwa2IxR0JFNXhRYThsTVVUIiwiYW1yIjpbInB3ZCIsInJzYSJdLCJhcHBpZCI6IjRiMGRiOGMyLTlmMjYtNDQxNy04YmRlLTNmMGUzNjU2ZjhlMCIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNzYxYjQ1ZTktMjA4MC00NTczLWJlYmItYjQzZDQzNWNlMWFmIiwiZmFtaWx5X25hbWUiOiJCYXNpYyBVc2VyIDEiLCJnaXZlbl9uYW1lIjoiQ2xvdWQgSURMQUIiLCJpcGFkZHIiOiI2OC4yMjYuNjcuMTIiLCJuYW1lIjoiQ2xvdWQgSURMQUIgQmFzaWMgVXNlciAxIiwib2lkIjoiNTk3Zjg2Y2QtMTNmMy00NGMwLWJlY2UtYTFlNzdiYTQzMjI4IiwicHVpZCI6IjEwMDMyMDAwOUM2NkIyMDUiLCJyaCI6IjAuQVJ3QWtxMUY5bzNqR2syMUVOR3dtblNveWdJQUFBQUFBQUFBd0FBQUFBQUFBQUFjQUNRLiIsInNjcCI6IkFncmVlbWVudC5SZWFkLkFsbCBBZ3JlZW1lbnQuUmVhZFdyaXRlLkFsbCBBZ3JlZW1lbnRBY2NlcHRhbmNlLlJlYWQgQWdyZWVtZW50QWNjZXB0YW5jZS5SZWFkLkFsbCBBcHBDYXRhbG9nLlJlYWRXcml0ZS5BbGwgQXVkaXRMb2cuUmVhZC5BbGwgQm9va2luZ3MuTWFuYWdlLkFsbCBCb29raW5ncy5SZWFkLkFsbCBCb29raW5ncy5SZWFkV3JpdGUuQWxsIEJvb2tpbmdzQXBwb2ludG1lbnQuUmVhZFdyaXRlLkFsbCBDYWxlbmRhcnMuUmVhZCBDYWxlbmRhcnMuUmVhZC5TaGFyZWQgQ2FsZW5kYXJzLlJlYWRXcml0ZSBDYWxlbmRhcnMuUmVhZFdyaXRlLlNoYXJlZCBDb250YWN0cy5SZWFkIENvbnRhY3RzLlJlYWQuU2hhcmVkIENvbnRhY3RzLlJlYWRXcml0ZSBDb250YWN0cy5SZWFkV3JpdGUuU2hhcmVkIERldmljZS5Db21tYW5kIERldmljZS5SZWFkIERldmljZU1hbmFnZW1lbnRBcHBzLlJlYWQuQWxsIERldmljZU1hbmFnZW1lbnRBcHBzLlJlYWRXcml0ZS5BbGwgRGV2aWNlTWFuYWdlbWVudENvbmZpZ3VyYXRpb24uUmVhZC5BbGwgRGV2aWNlTWFuYWdlbWVudENvbmZpZ3VyYXRpb24uUmVhZFdyaXRlLkFsbCBEZXZpY2VNYW5hZ2VtZW50TWFuYWdlZERldmljZXMuUHJpdmlsZWdlZE9wZXJhdGlvbnMuQWxsIERldmljZU1hbmFnZW1lbnRNYW5hZ2VkRGV2aWNlcy5SZWFkLkFsbCBEZXZpY2VNYW5hZ2VtZW50TWFuYWdlZERldmljZXMuUmVhZFdyaXRlLkFsbCBEZXZpY2VNYW5hZ2VtZW50UkJBQy5SZWFkLkFsbCBEZXZpY2VNYW5hZ2VtZW50UkJBQy5SZWFkV3JpdGUuQWxsIERldmljZU1hbmFnZW1lbnRTZXJ2aWNlQ29uZmlnLlJlYWQuQWxsIERldmljZU1hbmFnZW1lbnRTZXJ2aWNlQ29uZmlnLlJlYWRXcml0ZS5BbGwgRGlyZWN0b3J5LkFjY2Vzc0FzVXNlci5BbGwgRGlyZWN0b3J5LlJlYWQuQWxsIERpcmVjdG9yeS5SZWFkV3JpdGUuQWxsIEVBUy5BY2Nlc3NBc1VzZXIuQWxsIEVkdUFkbWluaXN0cmF0aW9uLlJlYWQgRWR1QWRtaW5pc3RyYXRpb24uUmVhZFdyaXRlIEVkdUFzc2lnbm1lbnRzLlJlYWQgRWR1QXNzaWdubWVudHMuUmVhZEJhc2ljIEVkdUFzc2lnbm1lbnRzLlJlYWRXcml0ZSBFZHVBc3NpZ25tZW50cy5SZWFkV3JpdGVCYXNpYyBFZHVSb3N0ZXIuUmVhZCBFZHVSb3N0ZXIuUmVhZEJhc2ljIEVkdVJvc3Rlci5SZWFkV3JpdGUgRmlsZXMuUmVhZCBGaWxlcy5SZWFkLkFsbCBGaWxlcy5SZWFkLlNlbGVjdGVkIEZpbGVzLlJlYWRXcml0ZSBGaWxlcy5SZWFkV3JpdGUuQWxsIEZpbGVzLlJlYWRXcml0ZS5BcHBGb2xkZXIgRmlsZXMuUmVhZFdyaXRlLlNlbGVjdGVkIEZpbmFuY2lhbHMuUmVhZFdyaXRlLkFsbCBHcm91cC5SZWFkLkFsbCBHcm91cC5SZWFkV3JpdGUuQWxsIElkZW50aXR5UHJvdmlkZXIuUmVhZC5BbGwgSWRlbnRpdHlQcm92aWRlci5SZWFkV3JpdGUuQWxsIElkZW50aXR5Umlza0V2ZW50LlJlYWQuQWxsIE1haWwuUmVhZCBNYWlsLlJlYWQuU2hhcmVkIE1haWwuUmVhZFdyaXRlIE1haWwuUmVhZFdyaXRlLlNoYXJlZCBNYWlsLlNlbmQgTWFpbC5TZW5kLlNoYXJlZCBNYWlsYm94U2V0dGluZ3MuUmVhZCBNYWlsYm94U2V0dGluZ3MuUmVhZFdyaXRlIE1lbWJlci5SZWFkLkhpZGRlbiBOb3Rlcy5DcmVhdGUgTm90ZXMuUmVhZCBOb3Rlcy5SZWFkLkFsbCBOb3Rlcy5SZWFkV3JpdGUgTm90ZXMuUmVhZFdyaXRlLkFsbCBOb3Rlcy5SZWFkV3JpdGUuQ3JlYXRlZEJ5QXBwIFBlb3BsZS5SZWFkIFBlb3BsZS5SZWFkLkFsbCBQcml2aWxlZ2VkQWNjZXNzLlJlYWRXcml0ZS5BenVyZUFEIFByaXZpbGVnZWRBY2Nlc3MuUmVhZFdyaXRlLkF6dXJlUmVzb3VyY2VzIFJlcG9ydHMuUmVhZC5BbGwgU2VjdXJpdHlFdmVudHMuUmVhZC5BbGwgU2VjdXJpdHlFdmVudHMuUmVhZFdyaXRlLkFsbCBTaXRlcy5GdWxsQ29udHJvbC5BbGwgU2l0ZXMuTWFuYWdlLkFsbCBTaXRlcy5SZWFkLkFsbCBTaXRlcy5SZWFkV3JpdGUuQWxsIFN1YnNjcmlwdGlvbi5SZWFkLkFsbCBUYXNrcy5SZWFkIFRhc2tzLlJlYWQuU2hhcmVkIFRhc2tzLlJlYWRXcml0ZSBUYXNrcy5SZWFkV3JpdGUuU2hhcmVkIFVzZXIuRXhwb3J0LkFsbCBVc2VyLkludml0ZS5BbGwgVXNlci5SZWFkIFVzZXIuUmVhZC5BbGwgVXNlci5SZWFkQmFzaWMuQWxsIFVzZXIuUmVhZFdyaXRlIFVzZXIuUmVhZFdyaXRlLkFsbCBVc2VyQWN0aXZpdHkuUmVhZFdyaXRlLkNyZWF0ZWRCeUFwcCBVc2VyVGltZWxpbmVBY3Rpdml0eS5Xcml0ZS5DcmVhdGVkQnlBcHAiLCJzdWIiOiJPZzgyUWk1OTJqLTlzUEQwZnM5dEJDeWpXMGlUSzVNY1ozT2VUbDlFbDlRIiwidGVuYW50X3JlZ2lvbl9zY29wZSI6Ik5BIiwidGlkIjoiZjY0NWFkOTItZTM4ZC00ZDFhLWI1MTAtZDFiMDlhNzRhOGNhIiwidW5pcXVlX25hbWUiOiJpZGxhYjFAbXNpZGxhYjQub25taWNyb3NvZnQuY29tIiwidXBuIjoiaWRsYWIxQG1zaWRsYWI0Lm9ubWljcm9zb2Z0LmNvbSIsInV0aSI6IjRLakI1T3BzVFVXSDRScEtGX1VpQUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfY2MiOlsiQ1AxIl19.UsxmiVwSy8oUbWnYF8Cp0niClgJVNpvxtK7IOz73sHdkY28FYA8zIdMPKBdOX5CufIUn59d78X4WevEPrK9USaeD67c7S0W76HUmRjgfVjqqOhNj15rPwYTHhpYbI_7hOw_Lo68H2q5V7joZx59sosW2Vy3zawob02EaQpvHh_cQp6cMF-oJt5JT8J4iQblo03KndRZVaNh_n3oVDQmMaEzur4tBB4lnUAoaExE6hNFOWi25h59PNctiPRGEBe17MrJqTNuey8zICsCnCrTG9yVe1GVEZ2aF2JXH-JsqTxxoCA7JVnpU4CwxN3ONLYeg-mLYwDtYxybelSvVV-LitA";

    static final String TEST_IDTOKEN_USERID = "4f859989-a2ff-411e-9048-c322247ac62c";

    static final String TEST_IDTOKEN_UPN = "admin@aaltests.onmicrosoft.com";


    private String mTestTag;

    private static final String TAG = "AuthenticationContextTest";

    @Before
    @SuppressLint("PackageManagerGetSignatures")
    public void setUp() throws Exception {
        Logger.d(TAG, "setup key at settings");

        System.setProperty(
                "dexmaker.dexcache",
                getInstrumentation()
                        .getTargetContext()
                        .getCacheDir()
                        .getPath()
        );

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

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

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

            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);
        addAzureADCloudForValidAuthority();

        AuthenticationSettings.INSTANCE.setUseBroker(false);
        // ADAL is set to this signature for now
        PackageInfo info = getInstrumentation().getContext().getPackageManager()
                .getPackageInfo(getInstrumentation().getContext().getPackageName(), PackageHelper.getPackageManagerSignaturesFlag());

        // Broker App can be signed with multiple certificates. It will look
        // all of them
        // until it finds the correct one for ADAL broker.
        for (Signature signature : PackageHelper.getSignatures(info)) {
            final byte[] testSignature = signature.toByteArray();
            MessageDigest md = MessageDigest.getInstance("SHA");
            md.update(testSignature);
            mTestTag = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
            break;
        }
    }

    @After
    public void tearDown() throws Exception {
        HttpUrlConnectionFactory.setMockedHttpUrlConnection(null);
        Logger.getInstance().setExternalLogger(null);
    }

    /**
     * Test constructor to make sure authority parameter is set.
     */
    @Test
    public void testConstructor() throws NoSuchAlgorithmException, NoSuchPaddingException {
        testAuthorityTrim("authorityFail");
        testAuthorityTrim("https://msft.com////");
        testAuthorityTrim("https:////");
        AuthenticationContext context2 = new AuthenticationContext(getInstrumentation().getContext(),
                "https://github.com/MSOpenTech/some/some", false);
        assertEquals("https://github.com/MSOpenTech", context2.getAuthority());
    }

    private void testAuthorityTrim(String authority) throws NoSuchAlgorithmException,
            NoSuchPaddingException {
        try {
            new AuthenticationContext(getInstrumentation().getContext(), authority, false);
            Assert.fail("expected to fail");
        } catch (IllegalArgumentException e) {
            assertTrue("authority in the msg", e.getMessage().contains("authority"));
        }
    }

    @Test
    public void testConstructorNoCache() throws UsageAuthenticationException {
        final String authority = "https://github.com/MSOpenTech";
        final AuthenticationContext context = new AuthenticationContext(getInstrumentation().getContext(), authority, false,
                null);
        assertNull(context.getCache());
    }

    @Test
    public void testClearAdalCacheRemovesReplicaCache() {
        // Init a Context
        final Context context = getInstrumentation().getContext();

        // Create an instance of the MSAL cache, populate it with some data
        final IPlatformComponents androidCommonComponents = AndroidPlatformComponentsFactory.createFromContext(context);
        final IAccountCredentialCache accountCredentialCache = new SharedPreferencesAccountCredentialCache(
                new CacheKeyValueDelegate(),
                androidCommonComponents.getStorageSupplier().getEncryptedNameValueStore(
                        DEFAULT_ACCOUNT_CREDENTIAL_SHARED_PREFERENCES,
                        String.class
                )
        );

        // Create a dummy account
        final AccountRecord dummyAccount = new AccountRecord();
        dummyAccount.setHomeAccountId("23bfff3f-2664-495d-8196-79e28eaf400b");
        dummyAccount.setEnvironment("login.windows.net");
        dummyAccount.setRealm("0b96cce5-7aef-4ecb-bb27-2e50e3ed69de");
        dummyAccount.setName("Garth Fort");

        // Save it
        accountCredentialCache.saveAccount(dummyAccount);

        // Assert that the dummy account exists
        assertEquals(dummyAccount, accountCredentialCache.getAccounts().get(0));

        // Create an instance of the Authentication Context
        final AuthenticationContext authContext =
                new AuthenticationContext(
                        getInstrumentation().getContext(),
                        "https://github.com/MSOpenTech/some/some",
                        false
                );

        // Clear the ADAL cache
        authContext.getCache().removeAll();

        // Assert that the replica is empty
        assertTrue(accountCredentialCache.getAccounts().isEmpty());
    }

    @Test
    public void testConstructorInternetPermission() throws NoSuchAlgorithmException,
            NoSuchPaddingException {
        String authority = "https://github.com/MSOpenTech";
        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        mockContext.addPermission("android.permission.INTERNET");

        // no exception
        new AuthenticationContext(mockContext, authority, false);

        try {
            mockContext.removePermission("android.permission.INTERNET");
            new AuthenticationContext(mockContext, authority, false);
            Assert.fail("Supposed to fail");
        } catch (Exception e) {

            assertEquals("Permission related message",
                    ADALError.DEVELOPER_INTERNET_PERMISSION_MISSING,
                    ((AuthenticationException) e.getCause()).getCode());
        }
    }

    @Test
    public void testConstructorValidateAuthority() throws NoSuchAlgorithmException,
            NoSuchPaddingException {

        String authority = "https://github.com/MSOpenTech";
        AuthenticationContext context = getAuthenticationContext(getInstrumentation().getContext(), authority, true,
                null);
        assertTrue("Validate flag is expected to be same", context.getValidateAuthority());

        context = new AuthenticationContext(getInstrumentation().getContext(), authority, false);
        assertFalse("Validate flag is expected to be same", context.getValidateAuthority());
    }

    @Test
    public void testCorrelationIdSetAndGet() throws NoSuchAlgorithmException,
            NoSuchPaddingException {
        UUID requestCorrelationId = UUID.randomUUID();
        AuthenticationContext context = new AuthenticationContext(getInstrumentation().getContext(), TEST_AUTHORITY,
                true);
        context.setRequestCorrelationId(requestCorrelationId);
        assertEquals("Verifier getter and setter", requestCorrelationId,
                context.getRequestCorrelationId());
    }

    /**
     * External call to Service to get real error response. Add expired item in
     * cache to try refresh token request. Web Request should have mCorrelationId
     * in the header.
     *
     * @throws InterruptedException
     */
    @Test
    @UiThreadTest
    public void testCorrelationIdInWebRequest() throws InterruptedException, IOException {

        Logger.getInstance().setEnablePII(true);
        AuthorityValidationMetadataCache.clearAuthorityValidationCache();
        final int minSDKVersionForTest = 15;
        if (Build.VERSION.SDK_INT <= minSDKVersionForTest) {
            Logger.v(TAG,
                    "Server is returning 401 status code without challenge. "
                            + "HttpUrlConnection does not return error stream for that in SDK 15. "
                            + "Without error stream, this test is useless.");
            return;
        }

        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        String expectedAccessToken = "TokenFortestAcquireToken" + UUID.randomUUID().toString();
        String expectedClientId = "client" + UUID.randomUUID().toString();
        String expectedResource = "resource" + UUID.randomUUID().toString();
        String expectedUser = "userid" + UUID.randomUUID().toString();
        final int cacheTimeOffsetMins = -30;
        ITokenCacheStore mockCache = getMockCache(cacheTimeOffsetMins, expectedAccessToken, expectedResource,
                expectedClientId, expectedUser, false);
        final AuthenticationContext context = getAuthenticationContext(mockContext,
                VALID_AUTHORITY, false, mockCache);

        UUID requestCorrelationId = UUID.randomUUID();
        Logger.d(TAG, "test correlationId:" + requestCorrelationId.toString());
        final CountDownLatch signal = new CountDownLatch(1);
        MockAuthenticationCallback callback = new MockAuthenticationCallback(signal);

        final String errorResponse = "{\"error\":\"invalid_request\", \"error_description\":\"Authentication failed. "
                + "correlation_id:" + requestCorrelationId.toString() + "\", \"correlation_id\": \""
                + requestCorrelationId.toString() + "\"}";

        final HttpURLConnection mockedConnection = Mockito.mock(HttpURLConnection.class);
        HttpUrlConnectionFactory.setMockedHttpUrlConnection(mockedConnection);
        Util.prepareMockedUrlConnection(mockedConnection);
        Mockito.when(mockedConnection.getOutputStream()).thenReturn(Mockito.mock(OutputStream.class));
        Mockito.when(mockedConnection.getInputStream()).thenThrow(IOException.class)
                .thenReturn(Util.createInputStream(errorResponse));
        Mockito.when(mockedConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_BAD_REQUEST);

        final TestLogResponse response = new TestLogResponse();
        response.listenLogForMessageSegments("Authentication failed", "correlation_id:\"\""
                + requestCorrelationId.toString());

        // Call acquire token with prompt never to prevent activity launch
        context.setRequestCorrelationId(requestCorrelationId);
        context.acquireTokenSilentAsync(expectedResource, expectedClientId, expectedUser, callback);
        signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

        // Verify that web request send correct headers
        Logger.i(TAG, "Get response message. ", "Response msg:" + response.getMessage());
        assertNotNull("Server response isn't null ", response.getMessage());
        assertTrue("Server response has same correlationId",
                response.getMessage().contains(requestCorrelationId.toString()));
    }

    /**
     * If package does not have declaration for activity, it should return false.
     */
    @Test
    public void testResolveIntent() throws InterruptedException {
        final FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        mockContext.setResolveIntent(false);

        final AuthenticationContext context = new AuthenticationContext(mockContext, VALID_AUTHORITY,
                false);
        final AggregatedTelemetryTestClass telemetry = new AggregatedTelemetryTestClass();
        Telemetry.getInstance().registerDispatcher(telemetry, true);

        final TestAuthCallback callback = new TestAuthCallback();
        context.acquireToken(Mockito.mock(Activity.class), "resource", "clientid", "redirect",
                PromptBehavior.Always, callback);
        final CountDownLatch signal = new CountDownLatch(1);
        signal.await(ACTIVITY_TIME_OUT, TimeUnit.MILLISECONDS);

        assertNotNull(callback.getCallbackException());
        assertTrue(callback.getCallbackException() instanceof AuthenticationException);

        final AuthenticationException authenticationException = (AuthenticationException) callback.getCallbackException();
        assertTrue(authenticationException.getCode() == ADALError.DEVELOPER_ACTIVITY_IS_NOT_RESOLVED);

        assertTrue(telemetry.eventsReceived());
    }

    /**
     * Test throws for different missing arguments
     */
    @Test
    public void testAcquireTokenNegativeArguments() throws NoSuchAlgorithmException,
            NoSuchPaddingException {
        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        final AuthenticationContext context = new AuthenticationContext(mockContext,
                VALID_AUTHORITY, false);
        final MockActivity testActivity = new MockActivity();
        final MockAuthenticationCallback testEmptyCallback = new MockAuthenticationCallback();

        AssertUtils.assertThrowsException(IllegalArgumentException.class, "callback",
                new Runnable() {

                    @Override
                    public void run() {
                        context.acquireToken(testActivity.getTestActivity(), "resource", "clientId", "redirectUri",
                                "userid", null);
                    }
                });

        AssertUtils.assertThrowsException(IllegalArgumentException.class, "resource",
                new Runnable() {

                    @Override
                    public void run() {
                        context.acquireToken(testActivity.getTestActivity(), null, "clientId", "redirectUri",
                                "userid", testEmptyCallback);
                    }
                });

        AssertUtils.assertThrowsException(IllegalArgumentException.class, "resource",
                new Runnable() {

                    @Override
                    public void run() {
                        context.acquireToken(testActivity.getTestActivity(), "", "clientId", "redirectUri", "userid",
                                testEmptyCallback);
                    }
                });

        AssertUtils.assertThrowsException(IllegalArgumentException.class, "clientid",
                new Runnable() {

                    @Override
                    public void run() {
                        context.acquireToken(testActivity.getTestActivity(), "resource", null, "redirectUri",
                                "userid", testEmptyCallback);
                    }
                });
    }

    @Test
    public void testAcquireTokenUserId() throws InterruptedException {
        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        AuthorityValidationMetadataCache.clearAuthorityValidationCache();
        final AuthenticationContext context = getAuthenticationContext(mockContext,
                "https://login.windows.net/common", false, null);

        final MockActivity testActivity = new MockActivity();
        final CountDownLatch signal = new CountDownLatch(1);
        MockAuthenticationCallback callback = new MockAuthenticationCallback(signal);
        testActivity.mSignal = signal;

        context.acquireToken(testActivity.getTestActivity(), "resource56", "clientId345", "redirect123", "userid123",
                callback);
        signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

        // verify request
        Intent intent = testActivity.mStartActivityIntent;
        assertNotNull(intent);
        Serializable request = intent
                .getSerializableExtra(AuthenticationConstants.Browser.REQUEST_MESSAGE);
        assertTrue(request.getClass().equals(AuthenticationRequest.class));

        final AuthenticationRequest authenticationRequest = (AuthenticationRequest) request;
        assertEquals("Redirect uri is same as package", "redirect123", authenticationRequest.getRedirectUri());
        assertEquals("login hint same as userid", "userid123", authenticationRequest.getLoginHint());
        assertEquals("client is same", "clientId345", authenticationRequest.getClientId());
        assertEquals("resource is same", "resource56", authenticationRequest.getResource());
    }

    @Test
    public void testEmptyRedirect() throws InterruptedException {
        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        final AuthenticationContext context = getAuthenticationContext(mockContext,
                "https://login.windows.net/common", false, null);

        final MockActivity testActivity = new MockActivity();
        final CountDownLatch signal = new CountDownLatch(1);
        MockAuthenticationCallback callback = new MockAuthenticationCallback(signal);
        testActivity.mSignal = signal;

        context.acquireToken(testActivity.getTestActivity(), "resource", "clientId", "", "userid", callback);
        signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

        Intent intent = testActivity.mStartActivityIntent;
        assertNotNull(intent);
        Serializable request = intent
                .getSerializableExtra(AuthenticationConstants.Browser.REQUEST_MESSAGE);
        assertTrue(request.getClass().equals(AuthenticationRequest.class));

        final AuthenticationRequest authenticationRequest = (AuthenticationRequest) request;
        assertEquals("Redirect uri is same as package", TEST_PACKAGE_NAME, authenticationRequest.getRedirectUri());
    }

    @Test
    public void testPrompt() throws InterruptedException {
        FileMockContext mockContext = new FileMockContext(getInstrumentation().getContext());
        final AuthenticationContext context = getAuthenticationContext(mockContext,
                "https://login.windows.net/common", false, null);

        final MockActivity testActivity = new MockActivity();
        final CountDownLatch signal = new CountDownLatch(1);
        MockAuthenticationCallback callback = new MockAuthenticationCallback(signal);
        testActivity.mSignal = signal;

        // 1 - Send prompt always
        context.acquireToken(testActivity.getTestActivity(), "testExtraParamsResource", "testExtraParamsClientId",
                "testExtraParamsredirectUri", PromptBehavior.Always, callback);
        signal.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

        // Get intent from activity to verify extraparams are send
        Intent intent = testActivity.mStartActivityIntent;
        assertNotNull(intent);
        final Serializable request = intent
                .getSerializableExtra(AuthenticationConstants.Browser.REQUEST_MESSAGE);
        assertTrue(request.getClass().equals(AuthenticationRequest.class));

        final AuthenticationRequest authenticationRequest = (AuthenticationRequest) request;
        assertEquals("Prompt param is same", PromptBehavior.Always, authenticationRequest.getPrompt());

        // 2 - Send refresh prompt
        final CountDownLatch signal2 = new CountDownLatch(1);
        MockAuthenticationCallback callback2 = new MockAuthenticationCallback(signal2);
        testActivity.mSignal = signal2;
        context.acquireToken(testActivity.getTestActivity(), "testExtraParamsResource", "testExtraParamsClientId",
                "testExtraParamsredirectUri", PromptBehavior.REFRESH_SESSION, callback2);
        signal2.await(CONTEXT_REQUEST_TIME_OUT, TimeUnit.MILLISECONDS);

        // Get intent from activity to verify extraparams are send
        intent = testActivity.mStartActivityIntent;
        assertNotNull(intent);
        final Serializable request2 = intent.getSerializableExtra(AuthenticationConstants.Browser.REQUEST_MESSAGE);
        assertTrue(request2.getClass().equals(AuthenticationRequest.class));

        final AuthenticationRequest authenticationRequest2 = (AuthenticationRequest) request2;
        assertEquals("Prompt param is same", PromptBehavior.REFRESH_SESSION, authenticationRequest2.getPrompt());
    }

    @Test
