Chromium Code Reviews| 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 |
| index 0044420ca1fb9e5108251a6b5313048dce179eda..5c71d4a244be55eeeb6084d8482d1b2b38dccb82 100644 |
| --- a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java |
| +++ b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java |
| @@ -9,38 +9,59 @@ |
| import static org.hamcrest.CoreMatchers.nullValue; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.fail; |
| +import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Matchers.anyLong; |
| import static org.mockito.Matchers.anyString; |
| import static org.mockito.Matchers.eq; |
| +import static org.mockito.Matchers.isNull; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| +import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| +import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| +import android.accounts.Account; |
| 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.app.Application; |
| +import android.content.BroadcastReceiver; |
| +import android.content.Context; |
| +import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.Handler; |
| +import junit.framework.Assert; |
| + |
| +import org.chromium.base.ApplicationStatus; |
| import org.chromium.base.BaseChromiumApplication; |
| import org.chromium.base.test.shadows.ShadowMultiDex; |
| +import org.chromium.net.HttpNegotiateAuthenticator.GetAccountsCallback; |
| +import org.chromium.net.HttpNegotiateAuthenticator.RequestData; |
| import org.chromium.testing.local.LocalRobolectricTestRunner; |
| +import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| +import org.mockito.ArgumentCaptor; |
| +import org.mockito.Captor; |
| +import org.mockito.Mock; |
| +import org.mockito.MockitoAnnotations; |
| 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 org.robolectric.shadows.ShadowApplication; |
| import java.io.IOException; |
| +import java.util.List; |
| /** |
| * Robolectric tests for HttpNegotiateAuthenticator |
| @@ -50,59 +71,35 @@ |
| shadows = {HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class, |
| ShadowMultiDex.class}) |
| public class HttpNegotiateAuthenticatorTest { |
| - private static class GetAuthTokenByFeaturesInvocation { |
|
dgn
2015/10/30 12:35:39
Not needed anymore since we use Mockito mocks inst
|
| - // Since the account manager is an SDK singleton (it is fetched using AccountManager.get()), |
| - // we can't validate its method calls with Mockito, so do so using our shadow method. |
| - int mCallCount; |
| - String mAccountTypeReceived; |
| - String mAuthTokenTypeReceived; |
| - String mFeaturesReceived[]; |
| - Bundle mAddAccountOptionsReceived; |
| - Bundle mAuthTokenOptionsReceived; |
| - AccountManagerCallback<Bundle> mCallbackReceived; |
| - Handler mHandlerReceived; |
| - |
| - public AccountManagerFuture<Bundle> getAuthTokenByFeatures(String accountType, |
| - String authTokenType, String[] features, Activity activity, |
| - Bundle addAccountOptions, Bundle getAuthTokenOptions, |
| - AccountManagerCallback<Bundle> callback, Handler handler) { |
| - mCallCount++; |
| - mAccountTypeReceived = accountType; |
| - mAuthTokenTypeReceived = authTokenType; |
| - mFeaturesReceived = features; |
| - mAddAccountOptionsReceived = addAccountOptions; |
| - mAuthTokenOptionsReceived = getAuthTokenOptions; |
| - mCallbackReceived = callback; |
| - mHandlerReceived = handler; |
| - |
| - return null; |
| - } |
| - } |
| - |
| - private static GetAuthTokenByFeaturesInvocation sInvocation; |
| - |
| /** |
| - * Robolectic's ShadowAccountManager doesn't implement getAccountsByTypeAndFeature so extend it. |
| - * We simply check the call is correct, and don't try to emulate it. This also allows us to do |
| - * more checking than we could using a vanilla shadow. |
| - * |
| + * User the AccountManager to inject a mock instance. |
| * 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) { |
| - return sInvocation.getAuthTokenByFeatures(accountType, authTokenType, features, |
| - activity, addAccountOptions, getAuthTokenOptions, callback, handler); |
| + public static AccountManager get(Context context) { |
| + return sMockAccountManager; |
| } |
| } |
| + @Mock |
| + private static AccountManager sMockAccountManager; |
| + @Captor |
| + private ArgumentCaptor<AccountManagerCallback<Bundle>> mBundleCallbackCaptor; |
|
dgn
2015/10/30 12:35:39
Captors are done that way to avoid unchecked cast
aberent
2015/10/30 16:38:10
nit: Comment to explain this?
dgn
2015/10/30 17:04:40
It's a completely standard way to declare captors,
|
| + @Captor |
| + private ArgumentCaptor<AccountManagerCallback<Account[]>> mAccountCallbackCaptor; |
| + @Captor |
| + private ArgumentCaptor<Bundle> mBundleCaptor; |
| + |
| @Before |
| public void setUp() { |
| - sInvocation = new GetAuthTokenByFeaturesInvocation(); |
| + MockitoAnnotations.initMocks(this); |
| + } |
| + |
| + @After |
| + public void tearDown() { |
| + ApplicationStatus.destroyForTesting(); |
| } |
| /** |
| @@ -110,35 +107,150 @@ public void setUp() { |
| */ |
| @Test |
| public void testGetNextAuthToken() { |
| - HttpNegotiateAuthenticator authenticator = |
| - HttpNegotiateAuthenticator.create("Dummy_Account"); |
| + final String accountType = "Dummy_Account"; |
| + HttpNegotiateAuthenticator authenticator = HttpNegotiateAuthenticator.create(accountType); |
| Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| + |
| authenticator.getNextAuthToken(0, "test_principal", "", true); |
| - assertThat( |
| - "getAuthTokenByFeatures called precisely once", sInvocation.mCallCount, equalTo(1)); |
| - assertThat("Received account type matches input", sInvocation.mAccountTypeReceived, |
| - equalTo("Dummy_Account")); |
| - assertThat("AuthTokenType is \"SPNEGO:HOSTBASED:test_principal\"", |
| - sInvocation.mAuthTokenTypeReceived, equalTo("SPNEGO:HOSTBASED:test_principal")); |
| - assertThat("Features are precisely {\"SPNEGO\"}", sInvocation.mFeaturesReceived, |
| - equalTo(new String[] {"SPNEGO"})); |
| - assertThat("No account options requested", sInvocation.mAddAccountOptionsReceived, |
| - nullValue()); |
| + |
| + verify(sMockAccountManager).getAuthTokenByFeatures( |
| + eq(accountType), |
| + eq("SPNEGO:HOSTBASED:test_principal"), |
| + eq(new String[] {"SPNEGO"}), |
| + any(Activity.class), |
| + isNull(Bundle.class), |
| + mBundleCaptor.capture(), |
| + mBundleCallbackCaptor.capture(), |
| + any(Handler.class)); |
| + |
| assertThat("There is no existing context", |
| - sInvocation.mAuthTokenOptionsReceived.get( |
| - HttpNegotiateConstants.KEY_SPNEGO_CONTEXT), |
| + mBundleCaptor.getValue().get(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT), |
| nullValue()); |
| assertThat("The existing token is empty", |
| - sInvocation.mAuthTokenOptionsReceived.getString( |
| - HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN), |
| + mBundleCaptor.getValue().getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN), |
| equalTo("")); |
| - assertThat("Delegation is allowed", sInvocation.mAuthTokenOptionsReceived.getBoolean( |
| - HttpNegotiateConstants.KEY_CAN_DELEGATE), |
| + assertThat("Delegation is allowed", |
| + mBundleCaptor.getValue().getBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE), |
| equalTo(true)); |
| assertThat("getAuthTokenByFeatures was called with a callback", |
| - sInvocation.mCallbackReceived, notNullValue()); |
| - assertThat("getAuthTokenByFeatures was called with a handler", sInvocation.mHandlerReceived, |
| - notNullValue()); |
| + mBundleCallbackCaptor.getValue(), notNullValue()); |
| + } |
| + |
| + /** |
| + * Test of {@link HttpNegotiateAuthenticator#getNextAuthToken} without a visible activity. |
| + * This emulates the behavior with WebView, where the application is a generic one and doesn't |
| + * set up the ApplicationStatus the same way. |
| + */ |
| + @Test |
| + @Config(application = Application.class) |
| + public void testGetNextAuthTokenWithoutActivity() { |
| + final String accountType = "Dummy_Account"; |
| + final Account[] returnedAccount = {new Account("name", accountType)}; |
| + HttpNegotiateAuthenticator authenticator = createWithoutNative(accountType); |
| + |
| + authenticator.getNextAuthToken(1234, "test_principal", "", true); |
| + |
| + Assert.assertNull(ApplicationStatus.getLastTrackedFocusedActivity()); |
| + verify(sMockAccountManager).getAccountsByTypeAndFeatures( |
| + eq(accountType), |
| + eq(new String[]{"SPNEGO"}), |
| + mAccountCallbackCaptor.capture(), |
| + any(Handler.class)); |
| + |
| + mAccountCallbackCaptor.getValue().run(makeFuture(returnedAccount)); |
| + |
| + verify(sMockAccountManager).getAuthToken( |
| + any(Account.class), |
| + eq("SPNEGO:HOSTBASED:test_principal"), |
| + mBundleCaptor.capture(), |
| + eq(true), |
| + any(HttpNegotiateAuthenticator.GetTokenCallback.class), |
| + any(Handler.class)); |
| + |
| + assertThat("There is no existing context", |
| + mBundleCaptor.getValue().get(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT), |
| + nullValue()); |
| + assertThat("The existing token is empty", |
| + mBundleCaptor.getValue().getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN), |
| + equalTo("")); |
| + assertThat("Delegation is allowed", |
| + mBundleCaptor.getValue().getBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE), |
| + equalTo(true)); |
| + } |
| + |
| + /** Tests the behavior of {@link HttpNegotiateAuthenticator.GetAccountsCallback} */ |
| + @Test |
| + public void testGetAccountCallback() { |
| + String type = "Dummy_Account"; |
| + HttpNegotiateAuthenticator authenticator = createWithoutNative(type); |
| + RequestData requestData = new RequestData(); |
| + requestData.nativeResultObject = 42; |
| + requestData.accountManager = sMockAccountManager; |
| + GetAccountsCallback callback = authenticator.new GetAccountsCallback(requestData); |
| + |
| + // Should fail because there are no accounts |
| + callback.run(makeFuture(new Account[]{})); |
| + verify(authenticator).nativeSetResult( |
| + eq(42L), |
| + eq(NetError.ERR_UNEXPECTED), |
| + isNull(String.class)); |
| + |
| + // Should succeed, for a single account we use it for the AccountManager#getAuthToken call. |
| + Account testAccount = new Account("a", type); |
| + callback.run(makeFuture(new Account[]{testAccount})); |
| + verify(sMockAccountManager).getAuthToken( |
| + eq(testAccount), |
| + anyString(), |
| + any(Bundle.class), |
| + eq(true), |
| + any(HttpNegotiateAuthenticator.GetTokenCallback.class), |
| + any(Handler.class)); |
| + |
| + // Should fail because there is more than one account |
| + callback.run(makeFuture(new Account[]{new Account("a", type), new Account("b", type)})); |
| + verify(authenticator, times(2)).nativeSetResult( |
| + eq(42L), |
| + eq(NetError.ERR_UNEXPECTED), |
| + isNull(String.class)); |
| + } |
| + |
| + /** |
| + * Tests the behavior of {@link HttpNegotiateAuthenticator.GetTokenCallback} when the result it |
| + * receives contains an intent rather than a token directly. |
| + */ |
| + @Test |
| + public void testGetTokenCallbackWithIntent() { |
| + String type = "Dummy_Account"; |
| + HttpNegotiateAuthenticator authenticator = createWithoutNative(type); |
| + RequestData requestData = new RequestData(); |
| + requestData.nativeResultObject = 42; |
| + requestData.authTokenType = "foo"; |
| + requestData.account = new Account("a", type); |
| + requestData.accountManager = sMockAccountManager; |
| + Bundle b = new Bundle(); |
| + b.putParcelable(AccountManager.KEY_INTENT, new Intent()); |
| + |
| + authenticator.new GetTokenCallback(requestData).run(makeFuture(b)); |
| + verifyZeroInteractions(sMockAccountManager); |
| + |
| + // Verify that the broadcast receiver is registered |
| + Intent intent = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); |
| + ShadowApplication shadowApplication = Robolectric.getShadowApplication(); |
| + List<BroadcastReceiver> receivers = shadowApplication.getReceiversForIntent(intent); |
| + assertThat("There is one registered broadcast receiver", receivers.size(), equalTo(1)); |
| + |
| + // Send the intent to the receiver. |
| + BroadcastReceiver receiver = receivers.get(0); |
| + receiver.onReceive(Robolectric.getShadowApplication().getApplicationContext(), intent); |
| + |
| + // Verify that the auth token is properly requested from the account manager. |
| + verify(sMockAccountManager).getAuthToken( |
| + eq(new Account("a", type)), |
| + eq("foo"), |
| + isNull(Bundle.class), |
| + eq(true), |
| + any(HttpNegotiateAuthenticator.GetTokenCallback.class), |
| + any(Handler.class)); |
| } |
| /** |
| @@ -146,105 +258,184 @@ public void testGetNextAuthToken() { |
| */ |
| @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()); |
| + HttpNegotiateAuthenticator authenticator = createWithoutNative("Dummy_Account"); |
| Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| // Call getNextAuthToken to get the callback |
| authenticator.getNextAuthToken(1234, "test_principal", "", true); |
| + verify(sMockAccountManager) |
| + .getAuthTokenByFeatures(anyString(), anyString(), any(String[].class), |
| + any(Activity.class), any(Bundle.class), any(Bundle.class), |
| + mBundleCallbackCaptor.capture(), any(Handler.class)); |
| - // 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(); |
| - } |
| - sInvocation.mCallbackReceived.run(accountManagerFuture); |
| + mBundleCallbackCaptor.getValue().run(makeFuture(resultBundle)); |
| verify(authenticator).nativeSetResult(1234, 0, "output_token"); |
| // Check that the next call to getNextAuthToken uses the correct context |
| authenticator.getNextAuthToken(5678, "test_principal", "", true); |
| + verify(sMockAccountManager, times(2)) |
| + .getAuthTokenByFeatures(anyString(), anyString(), any(String[].class), |
| + any(Activity.class), any(Bundle.class), mBundleCaptor.capture(), |
| + mBundleCallbackCaptor.capture(), any(Handler.class)); |
| + |
| assertThat("The spnego context is preserved between calls", |
| - sInvocation.mAuthTokenOptionsReceived.getBundle( |
| - HttpNegotiateConstants.KEY_SPNEGO_CONTEXT), |
| + mBundleCaptor.getValue().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(); |
| - } |
| - sInvocation.mCallbackReceived.run(accountManagerFuture); |
| - verify(authenticator).nativeSetResult(5678, NetError.ERR_ABORTED, null); |
| + mBundleCallbackCaptor.getValue().run( |
| + this.<Bundle>makeFuture(new OperationCanceledException())); |
| + verify(authenticator).nativeSetResult(5678, NetError.ERR_UNEXPECTED, 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()); |
| - |
| + @Test |
| + public void testAccountManagerCallbackNullErrorReturns() { |
| 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(); |
| - } |
| - sInvocation.mCallbackReceived.run(accountManagerFuture); |
| - verify(authenticator).nativeSetResult(anyLong(), eq(expectedError), anyString()); |
| + checkErrorReturn(null, NetError.ERR_UNEXPECTED); |
| } |
| - /** |
| - * Test of callback error returns when getting the auth token completes. |
| - */ |
| @Test |
| - public void testAccountManagerCallbackErrorReturns() { |
|
dgn
2015/10/30 12:35:39
Split to reset the state of the mocks between test
|
| - checkErrorReturn(null, NetError.ERR_UNEXPECTED); |
| + public void testAccountManagerCallbackUnexpectedErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED, NetError.ERR_UNEXPECTED); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackAbortedErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_ABORTED, NetError.ERR_ABORTED); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackSecLibErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS, |
| NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackInvalidResponseErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn( |
| HttpNegotiateConstants.ERR_INVALID_RESPONSE, NetError.ERR_INVALID_RESPONSE); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackInvalidAuthCredsErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS, |
| NetError.ERR_INVALID_AUTH_CREDENTIALS); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackUnsuppAutchSchemeErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME, |
| NetError.ERR_UNSUPPORTED_AUTH_SCHEME); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackMissingAuthCredsErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS, |
| NetError.ERR_MISSING_AUTH_CREDENTIALS); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackUndocSecLibErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn(HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS, |
| NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackMalformedIdentityErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| checkErrorReturn( |
| HttpNegotiateConstants.ERR_MALFORMED_IDENTITY, NetError.ERR_MALFORMED_IDENTITY); |
| + } |
| + |
| + @Test |
| + public void testAccountManagerCallbackInvalidErrorReturns() { |
| + Robolectric.buildActivity(Activity.class).create().start().resume().visible(); |
| // 9999 is not a valid return value |
| checkErrorReturn(9999, NetError.ERR_UNEXPECTED); |
| } |
| + |
| + private void checkErrorReturn(Integer spnegoError, int expectedError) { |
| + HttpNegotiateAuthenticator authenticator = createWithoutNative("Dummy_Account"); |
| + |
| + // Call getNextAuthToken to get the callback |
| + authenticator.getNextAuthToken(1234, "test_principal", "", true); |
| + verify(sMockAccountManager).getAuthTokenByFeatures( |
| + anyString(), |
| + anyString(), |
| + any(String[].class), |
| + any(Activity.class), |
| + any(Bundle.class), |
| + any(Bundle.class), |
| + mBundleCallbackCaptor.capture(), |
| + any(Handler.class)); |
| + |
| + Bundle resultBundle = new Bundle(); |
| + if (spnegoError != null) { |
| + resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, spnegoError); |
| + } |
| + mBundleCallbackCaptor.getValue().run(makeFuture(resultBundle)); |
| + verify(authenticator).nativeSetResult(anyLong(), eq(expectedError), anyString()); |
| + } |
| + |
| + /** |
| + * Returns a future that successfully returns the provided result. |
| + * Hides mocking related annoyances: compiler warnings and irrelevant catch clauses. |
| + */ |
| + private <T> AccountManagerFuture<T> makeFuture(T result) { |
| + // 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<T> accountManagerFuture = mock(AccountManagerFuture.class); |
| + try { |
| + when(accountManagerFuture.getResult()).thenReturn(result); |
| + } catch (OperationCanceledException | AuthenticatorException | IOException e) { |
| + // Can never happen - artifact of Mockito. |
| + fail(); |
| + } |
| + return accountManagerFuture; |
| + } |
| + |
| + /** |
| + * Returns a future that fails with the provided exception when trying to get its result. |
| + * Hides mocking related annoyances: compiler warnings and irrelevant catch clauses. |
| + */ |
| + private <T> AccountManagerFuture<T> makeFuture(Exception ex) { |
| + // 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<T> accountManagerFuture = mock(AccountManagerFuture.class); |
| + try { |
| + when(accountManagerFuture.getResult()).thenThrow(ex); |
| + } catch (OperationCanceledException | AuthenticatorException | IOException e) { |
| + // Can never happen - artifact of Mockito. |
| + fail(); |
| + } |
| + return accountManagerFuture; |
| + } |
| + |
| + /** |
| + * Returns a new authenticator as a spy so that we can override and intercept the native method |
| + * calls. |
| + */ |
| + private HttpNegotiateAuthenticator createWithoutNative(String accountType) { |
| + HttpNegotiateAuthenticator authenticator = |
| + spy(HttpNegotiateAuthenticator.create(accountType)); |
| + doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString()); |
| + return authenticator; |
| + } |
| } |