| Index: net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
|
| diff --git a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..86474073623dc6edf7afd148041e0a08f55ae130
|
| --- /dev/null
|
| +++ b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
|
| @@ -0,0 +1,222 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.net;
|
| +
|
| +import static org.hamcrest.CoreMatchers.equalTo;
|
| +import static org.hamcrest.CoreMatchers.notNullValue;
|
| +import static org.hamcrest.CoreMatchers.nullValue;
|
| +import static org.junit.Assert.assertThat;
|
| +import static org.junit.Assert.fail;
|
| +import static org.mockito.Matchers.anyInt;
|
| +import static org.mockito.Matchers.anyLong;
|
| +import static org.mockito.Matchers.anyString;
|
| +import static org.mockito.Mockito.doNothing;
|
| +import static org.mockito.Mockito.eq;
|
| +import static org.mockito.Mockito.mock;
|
| +import static org.mockito.Mockito.spy;
|
| +import static org.mockito.Mockito.verify;
|
| +import static org.mockito.Mockito.when;
|
| +
|
| +import android.accounts.AccountManager;
|
| +import android.accounts.AccountManagerCallback;
|
| +import android.accounts.AccountManagerFuture;
|
| +import android.accounts.AuthenticatorException;
|
| +import android.accounts.OperationCanceledException;
|
| +import android.app.Activity;
|
| +import android.os.Bundle;
|
| +import android.os.Handler;
|
| +
|
| +import org.chromium.base.BaseChromiumApplication;
|
| +import org.chromium.testing.local.LocalRobolectricTestRunner;
|
| +import org.junit.Test;
|
| +import org.junit.runner.RunWith;
|
| +import org.robolectric.Robolectric;
|
| +import org.robolectric.annotation.Config;
|
| +import org.robolectric.annotation.Implementation;
|
| +import org.robolectric.annotation.Implements;
|
| +import org.robolectric.shadows.ShadowAccountManager;
|
| +
|
| +import java.io.IOException;
|
| +
|
| +/**
|
| + * Robolectric tests for HttpNegotiateAuthenticator
|
| + */
|
| +@RunWith(LocalRobolectricTestRunner.class)
|
| +@Config(manifest = Config.NONE,
|
| + shadows = HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class,
|
| + application = BaseChromiumApplication.class)
|
| +public class HttpNegotiateAuthenticatorTest {
|
| + static int sCallCount = 0;
|
| + static String sAccountTypeReceived;
|
| + static String sAuthTokenTypeReceived;
|
| + static String sFeaturesReceived[];
|
| + static Bundle sAddAccountOptionsReceived;
|
| + static Bundle sAuthTokenOptionsReceived;
|
| + static AccountManagerCallback<Bundle> sCallbackReceived;
|
| + static Handler sHandlerReceived;
|
| +
|
| + /**
|
| + * Robolectic's ShadowAccountManager doesn't implement getAccountsByTypeAndFeature so extend it.
|
| + * We simply check the call is correct, and don't try to emulate it Note: Shadow classes need to
|
| + * be public and static.
|
| + */
|
| + @Implements(AccountManager.class)
|
| + public static class ExtendedShadowAccountManager extends ShadowAccountManager {
|
| + @Implementation
|
| + public AccountManagerFuture<Bundle> getAuthTokenByFeatures(String accountType,
|
| + String authTokenType, String[] features, Activity activity,
|
| + Bundle addAccountOptions, Bundle getAuthTokenOptions,
|
| + AccountManagerCallback<Bundle> callback, Handler handler) {
|
| + sCallCount++;
|
| + sAccountTypeReceived = accountType;
|
| + sAuthTokenTypeReceived = authTokenType;
|
| + sFeaturesReceived = features;
|
| + sAddAccountOptionsReceived = addAccountOptions;
|
| + sAuthTokenOptionsReceived = getAuthTokenOptions;
|
| + sCallbackReceived = callback;
|
| + sHandlerReceived = handler;
|
| +
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Test of {@link HttpNegotiateAuthenticator#getNextAuthToken}
|
| + */
|
| + @Test
|
| + public void testGetNextAuthToken() {
|
| + HttpNegotiateAuthenticator authenticator =
|
| + HttpNegotiateAuthenticator.create("Dummy_Account");
|
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible();
|
| + authenticator.getNextAuthToken(0, "test_principal", "", true);
|
| + assertThat("getAuthTokenByFeatures called precisely once", sCallCount, equalTo(1));
|
| + assertThat("Received account type matches input", sAccountTypeReceived,
|
| + equalTo("Dummy_Account"));
|
| + assertThat("AuthTokenType is \"SPNEGO:HOSTBASED:test_principal\"", sAuthTokenTypeReceived,
|
| + equalTo("SPNEGO:HOSTBASED:test_principal"));
|
| + assertThat("Features are precisely {\"SPNEGO\"}", sFeaturesReceived,
|
| + equalTo(new String[] {"SPNEGO"}));
|
| + assertThat("No account options requested", sAddAccountOptionsReceived, nullValue());
|
| + assertThat("There is no existing context",
|
| + sAuthTokenOptionsReceived.get(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
|
| + nullValue());
|
| + assertThat("The existing token is empty",
|
| + sAuthTokenOptionsReceived.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN),
|
| + equalTo(""));
|
| + assertThat("Delegation is allowed",
|
| + sAuthTokenOptionsReceived.getBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE),
|
| + equalTo(true));
|
| + assertThat("getAuthTokenByFeatures was called with a callback", sCallbackReceived,
|
| + notNullValue());
|
| + assertThat("getAuthTokenByFeatures was called with a handler", sHandlerReceived,
|
| + notNullValue());
|
| + }
|
| +
|
| + /**
|
| + * Test of callback called when getting the auth token completes.
|
| + */
|
| + @Test
|
| + public void testAccountManagerCallbackRun() {
|
| + // Spy on the authenticator so that we can override and intercept the native method call.
|
| + HttpNegotiateAuthenticator authenticator =
|
| + spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
|
| + doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
|
| +
|
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible();
|
| +
|
| + // Call getNextAuthToken to get the callback
|
| + authenticator.getNextAuthToken(1234, "test_principal", "", true);
|
| +
|
| + // Avoid warning when creating mock accountManagerFuture, can't take .class of an
|
| + // instantiated generic type, yet compiler complains if I leave it uninstantiated.
|
| + @SuppressWarnings("unchecked")
|
| + AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
|
| + Bundle resultBundle = new Bundle();
|
| + Bundle context = new Bundle();
|
| + context.putString("String", "test_context");
|
| + resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants.OK);
|
| + resultBundle.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, context);
|
| + resultBundle.putString(AccountManager.KEY_AUTHTOKEN, "output_token");
|
| + try {
|
| + when(accountManagerFuture.getResult()).thenReturn(resultBundle);
|
| + } catch (OperationCanceledException | AuthenticatorException | IOException e) {
|
| + // Can never happen - artifact of Mockito.
|
| + fail();
|
| + }
|
| + sCallbackReceived.run(accountManagerFuture);
|
| + verify(authenticator).nativeSetResult(1234, 0, "output_token");
|
| +
|
| + // Check that the next call to getNextAuthToken uses the correct context
|
| + authenticator.getNextAuthToken(5678, "test_principal", "", true);
|
| + assertThat("The spnego context is preserved between calls",
|
| + sAuthTokenOptionsReceived.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
|
| + equalTo(context));
|
| +
|
| + // Test exception path
|
| + try {
|
| + when(accountManagerFuture.getResult()).thenThrow(new OperationCanceledException());
|
| + } catch (OperationCanceledException | AuthenticatorException | IOException e) {
|
| + // Can never happen - artifact of Mockito.
|
| + fail();
|
| + }
|
| + sCallbackReceived.run(accountManagerFuture);
|
| + verify(authenticator).nativeSetResult(5678, NetError.ERR_ABORTED, null);
|
| + }
|
| +
|
| + private void checkErrorReturn(Integer spnegoError, int expectedError) {
|
| + // Spy on the authenticator so that we can override and intercept the native method call.
|
| + HttpNegotiateAuthenticator authenticator =
|
| + spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
|
| + doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
|
| +
|
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible();
|
| +
|
| + // Call getNextAuthToken to get the callback
|
| + authenticator.getNextAuthToken(1234, "test_principal", "", true);
|
| +
|
| + // Avoid warning when creating mock accountManagerFuture, can't take .class of an
|
| + // instantiated generic type, yet compiler complains if I leave it uninstantiated.
|
| + @SuppressWarnings("unchecked")
|
| + AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
|
| + Bundle resultBundle = new Bundle();
|
| + if (spnegoError != null) {
|
| + resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, spnegoError);
|
| + }
|
| + try {
|
| + when(accountManagerFuture.getResult()).thenReturn(resultBundle);
|
| + } catch (OperationCanceledException | AuthenticatorException | IOException e) {
|
| + // Can never happen - artifact of Mockito.
|
| + fail();
|
| + }
|
| + sCallbackReceived.run(accountManagerFuture);
|
| + verify(authenticator).nativeSetResult(anyLong(), eq(expectedError), anyString());
|
| + }
|
| +
|
| + /**
|
| + * Test of callback error returns when getting the auth token completes.
|
| + */
|
| + @Test
|
| + public void testAccountManagerCallbackErrorReturns() {
|
| + checkErrorReturn(null, NetError.ERR_UNEXPECTED);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED, NetError.ERR_UNEXPECTED);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_ABORTED, NetError.ERR_ABORTED);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS,
|
| + NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS);
|
| + checkErrorReturn(
|
| + HttpNegotiateConstants.ERR_INVALID_RESPONSE, NetError.ERR_INVALID_RESPONSE);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS,
|
| + NetError.ERR_INVALID_AUTH_CREDENTIALS);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME,
|
| + NetError.ERR_UNSUPPORTED_AUTH_SCHEME);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS,
|
| + NetError.ERR_MISSING_AUTH_CREDENTIALS);
|
| + checkErrorReturn(HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS,
|
| + NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS);
|
| + checkErrorReturn(
|
| + HttpNegotiateConstants.ERR_MALFORMED_IDENTITY, NetError.ERR_MALFORMED_IDENTITY);
|
| + // 9999 is not a valid return value
|
| + checkErrorReturn(9999, NetError.ERR_UNEXPECTED);
|
| + }
|
| +}
|
|
|