/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright 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.facebook.login; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import com.facebook.AccessToken; import com.facebook.AccessTokenSource; import com.facebook.FacebookActivity; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.FacebookPowerMockTestCase; import com.facebook.FacebookSdk; import com.facebook.FacebookSdkNotInitializedException; import com.facebook.Profile; import com.facebook.internal.CallbackManagerImpl; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.internal.util.collections.Sets; import org.powermock.core.classloader.annotations.PrepareForTest; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.*; @PrepareForTest({ FacebookSdk.class, AccessToken.class, Profile.class}) public class LoginManagerTest extends FacebookPowerMockTestCase { private static final String MOCK_APP_ID = "1234"; private static final String USER_ID = "1000"; private final String TOKEN_STRING = "A token of my esteem"; private final List<String> PERMISSIONS = Arrays.asList("walk", "chew gum"); private final Date EXPIRES = new Date(2025, 5, 3); private final Date LAST_REFRESH = new Date(2023, 8, 15); @Mock private Activity mockActivity; @Mock private Fragment mockFragment; @Mock private Context mockApplicationContext; @Mock private PackageManager mockPackageManager; @Mock private FacebookCallback<LoginResult> mockCallback; @Mock private ThreadPoolExecutor threadExecutor; @Mock private FragmentActivity mockFragmentActivity; @Before public void before() throws Exception { mockStatic(FacebookSdk.class); stub(method(AccessToken.class, "getCurrentAccessToken")).toReturn(null); stub(method(AccessToken.class, "setCurrentAccessToken")).toReturn(null); stub(method(Profile.class, "fetchProfileForCurrentAccessToken")).toReturn(null); when(FacebookSdk.isInitialized()).thenReturn(true); when(FacebookSdk.getApplicationId()).thenReturn(MOCK_APP_ID); when(FacebookSdk.getApplicationContext()).thenReturn(mockApplicationContext); when(FacebookSdk.getExecutor()).thenReturn(threadExecutor); when(mockFragment.getActivity()).thenReturn(mockFragmentActivity); when(mockActivity.getApplicationContext()).thenReturn(mockApplicationContext); // We use mocks rather than RobolectricPackageManager because it's simpler to not // have to specify Intents. Default to resolving all intents to something. ResolveInfo resolveInfo = new ResolveInfo(); when(mockApplicationContext.getPackageManager()).thenReturn(mockPackageManager); when(mockPackageManager.resolveActivity(any(Intent.class), anyInt())) .thenReturn(resolveInfo); } @Test public void testRequiresSdkToBeInitialized() { try { when(FacebookSdk.isInitialized()).thenReturn(false); LoginManager loginManager = new LoginManager(); fail(); } catch (FacebookSdkNotInitializedException exception) { } } @Test public void testGetInstance() { LoginManager loginManager = LoginManager.getInstance(); assertNotNull(loginManager); } @Test public void testLoginBehaviorDefaultsToSsoWithFallback() { LoginManager loginManager = new LoginManager(); assertEquals(LoginBehavior.NATIVE_WITH_FALLBACK, loginManager.getLoginBehavior()); } @Test public void testCanChangeLoginBehavior() { LoginManager loginManager = new LoginManager(); loginManager.setLoginBehavior(LoginBehavior.NATIVE_ONLY); assertEquals(LoginBehavior.NATIVE_ONLY, loginManager.getLoginBehavior()); } @Test public void testDefaultAudienceDefaultsToFriends() { LoginManager loginManager = new LoginManager(); assertEquals(DefaultAudience.FRIENDS, loginManager.getDefaultAudience()); } @Test public void testCanChangeDefaultAudience() { LoginManager loginManager = new LoginManager(); loginManager.setDefaultAudience(DefaultAudience.EVERYONE); assertEquals(DefaultAudience.EVERYONE, loginManager.getDefaultAudience()); } @Test public void testLogInWithReadAndActivityThrowsIfPublishPermissionGiven() { LoginManager loginManager = new LoginManager(); try { loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "publish_actions")); fail(); } catch(FacebookException exception) { } } @Test public void testLogInWithPublishAndActivityThrowsIfPublishPermissionGiven() { LoginManager loginManager = new LoginManager(); try { loginManager.logInWithPublishPermissions(mockActivity, Arrays.asList("public_profile", "publish_actions")); fail(); } catch(FacebookException exception) { } } @Test public void testLogInThrowsIfCannotResolveFacebookActivity() { when(mockPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(null); LoginManager loginManager = new LoginManager(); try { loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); fail(); } catch(FacebookException exception) { } } @Test public void testLogInThrowsIfCannotStartFacebookActivity() { doThrow(new ActivityNotFoundException()).when(mockActivity) .startActivityForResult(any(Intent.class), anyInt()); LoginManager loginManager = new LoginManager(); try { loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); fail(); } catch(FacebookException exception) { } } @Test public void testRequiresNonNullActivity() { try { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions((Activity) null, Arrays.asList("public_profile", "user_friends")); fail(); } catch (NullPointerException exception) { } } @Test public void testRequiresNonNullFragment() { try { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions((Fragment) null, Arrays.asList("public_profile", "user_friends")); fail(); } catch (NullPointerException exception) { } } @Test public void testLogInWithReadDoesNotThrowWithReadPermissions() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); } @Test public void testLogInWithReadListCreatesPendingRequestWithCorrectValues() { LoginManager loginManager = new LoginManager(); // Change some defaults so we can verify the pending request picks them up. loginManager.setLoginBehavior(LoginBehavior.NATIVE_ONLY); loginManager.setDefaultAudience(DefaultAudience.EVERYONE); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); implTestLogInCreatesPendingRequestWithCorrectValues(loginManager, Arrays.asList("public_profile", "user_friends")); } @Test public void testLogInWithReadAndAccessTokenCreatesReauthRequest() { AccessToken accessToken = createAccessToken(); stub(method(AccessToken.class, "getCurrentAccessToken")).toReturn(accessToken); LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); int loginRequestCode = CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode(); verify(mockActivity, times(1)).startActivityForResult(any(Intent.class), eq(loginRequestCode)); } public void implTestLogInCreatesPendingRequestWithCorrectValues( LoginManager loginManager, final Collection<String> expectedPermissions) { ArgumentMatcher<Intent> m = new ArgumentMatcher<Intent>() { @Override public boolean matches(Object argument) { Intent orig = (Intent)argument; Bundle bundle = orig.getBundleExtra(LoginFragment.REQUEST_KEY); LoginClient.Request request = (LoginClient.Request)bundle.getParcelable(LoginFragment.EXTRA_REQUEST); assertEquals(MOCK_APP_ID, request.getApplicationId()); assertEquals(LoginBehavior.NATIVE_ONLY, request.getLoginBehavior()); assertEquals(DefaultAudience.EVERYONE, request.getDefaultAudience()); Set<String> permissions = request.getPermissions(); for (String permission : expectedPermissions) { assertTrue(permissions.contains(permission)); } return true; } }; int loginRequestCode = CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode(); verify(mockActivity, times(1)).startActivityForResult(argThat(m), eq(loginRequestCode)); } @Test public void testLogInWithReadAndActivityStartsFacebookActivityWithCorrectRequest() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mockActivity).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); Intent intent = intentArgumentCaptor.getValue(); ComponentName componentName = intent.getComponent(); assertEquals(FacebookActivity.class.getName(), componentName.getClassName()); assertEquals(LoginBehavior.NATIVE_WITH_FALLBACK.name(), intent.getAction()); } @Test public void testLogInWithReadAndFragmentStartsFacebookActivityWithCorrectRequest() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockFragment, Arrays.asList("public_profile", "user_friends")); ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mockFragment).startActivityForResult(intentArgumentCaptor.capture(), anyInt()); Intent intent = intentArgumentCaptor.getValue(); ComponentName componentName = intent.getComponent(); assertEquals(FacebookActivity.class.getName(), componentName.getClassName()); assertEquals(LoginBehavior.NATIVE_WITH_FALLBACK.name(), intent.getAction()); } @Test public void testLogInWitPublishDoesNotThrowWithPublishPermissions() { LoginManager loginManager = new LoginManager(); loginManager.logInWithPublishPermissions(mockActivity, Arrays.asList("publish_actions", "publish_stream")); } @Test public void testLogInWithPublishListCreatesPendingRequestWithCorrectValues() { LoginManager loginManager = new LoginManager(); // Change some defaults so we can verify the pending request picks them up. loginManager.setLoginBehavior(LoginBehavior.NATIVE_ONLY); loginManager.setDefaultAudience(DefaultAudience.EVERYONE); loginManager.logInWithPublishPermissions(mockActivity, Arrays.asList("publish_actions", "publish_stream")); implTestLogInCreatesPendingRequestWithCorrectValues(loginManager, Arrays.asList("publish_actions", "publish_stream")); } @Test public void testLogInWithPublishAndAccessTokenCreatesReauthRequest() { AccessToken accessToken = createAccessToken(); stub(method(AccessToken.class, "getCurrentAccessToken")).toReturn(accessToken); LoginManager loginManager = new LoginManager(); loginManager.logInWithPublishPermissions(mockActivity, Arrays.asList("publish_actions", "publish_stream")); int loginRequestCode = CallbackManagerImpl.RequestCodeOffset.Login.toRequestCode(); verify(mockActivity, times(1)).startActivityForResult(any(Intent.class), eq(loginRequestCode)); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnCancelResultCode() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_CANCELED, null, mockCallback); assertTrue(result); verify(mockCallback, times(1)).onCancel(); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnCancelResultCodeEvenWithData() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); Intent intent = createSuccessResultIntent(); boolean result = loginManager.onActivityResult( Activity.RESULT_CANCELED, intent, mockCallback); assertTrue(result); verify(mockCallback, times(1)).onCancel(); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); } @Test public void testOnActivityResultDoesNotModifyCurrentAccessTokenOnCancelResultCode() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); loginManager.onActivityResult(Activity.RESULT_CANCELED, null, mockCallback); verifyStatic(never()); AccessToken.setCurrentAccessToken(any(AccessToken.class)); } @Test public void testOnActivityResultHandlesMissingCallbackOnCancelResultCode() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_CANCELED, null); assertTrue(result); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnNullData() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, null, mockCallback); assertTrue(result); verify(mockCallback, times(1)).onError(isA(FacebookException.class)); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnMissingResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); Intent intent = createSuccessResultIntent(); intent.removeExtra(LoginFragment.RESULT_KEY); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, intent, mockCallback); assertTrue(result); verify(mockCallback, times(1)).onError(isA(FacebookException.class)); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnErrorResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, createErrorResultIntent(), mockCallback); ArgumentCaptor<FacebookException> exceptionArgumentCaptor = ArgumentCaptor.forClass(FacebookException.class); assertTrue(result); verify(mockCallback, times(1)).onError(exceptionArgumentCaptor.capture()); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); assertEquals("foo: bar", exceptionArgumentCaptor.getValue().getMessage()); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnCancelResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_CANCELED, createCancelResultIntent(), mockCallback); assertTrue(result); verify(mockCallback, times(1)).onCancel(); verify(mockCallback, never()).onSuccess(isA(LoginResult.class)); } @Test public void testOnActivityResultDoesNotModifyCurrentAccessTokenOnErrorResultCode() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); loginManager.onActivityResult( Activity.RESULT_CANCELED, createErrorResultIntent(), mockCallback); verifyStatic(never()); AccessToken.setCurrentAccessToken(any(AccessToken.class)); } @Test public void testOnActivityResultReturnsTrueAndCallsCallbackOnSuccessResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, createSuccessResultIntent(), mockCallback); assertTrue(result); verify(mockCallback, never()).onError(any(FacebookException.class)); verify(mockCallback, times(1)).onSuccess(isA(LoginResult.class)); } @Test public void testOnHandlesMissingCallbackkOnSuccessResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, createSuccessResultIntent(), null); assertTrue(result); } @Test public void testOnActivityResultSetsCurrentAccessTokenOnSuccessResult() { LoginManager loginManager = new LoginManager(); loginManager.logInWithReadPermissions(mockActivity, Arrays.asList("public_profile", "user_friends")); boolean result = loginManager.onActivityResult( Activity.RESULT_OK, createSuccessResultIntent(), mockCallback); verifyStatic(times(1)); AccessToken.setCurrentAccessToken(eq(createAccessToken())); } private Intent createSuccessResultIntent() { Intent intent = new Intent(); Set<String> permissions = Sets.newSet("public_profile", "user_friends"); LoginClient.Request request = new LoginClient.Request(null, permissions, null, null, null); AccessToken accessToken = createAccessToken(); LoginClient.Result result = LoginClient.Result.createTokenResult(request, accessToken); intent.putExtra(LoginFragment.RESULT_KEY, result); return intent; } private Intent createErrorResultIntent() { Intent intent = new Intent(); LoginClient.Request request = mock(LoginClient.Request.class); LoginClient.Result result = LoginClient.Result.createErrorResult(request, "foo", "bar"); intent.putExtra(LoginFragment.RESULT_KEY, result); return intent; } private Intent createCancelResultIntent() { Intent intent = new Intent(); LoginClient.Request request = mock(LoginClient.Request.class); LoginClient.Result result = LoginClient.Result.createCancelResult(request, null); intent.putExtra(LoginFragment.RESULT_KEY, result); return intent; } private AccessToken createAccessToken() { return new AccessToken( TOKEN_STRING, MOCK_APP_ID, USER_ID, PERMISSIONS, null, AccessTokenSource.WEB_VIEW, EXPIRES, LAST_REFRESH); } }