com.facebook.SessionTests.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.SessionTests.java

Source

/**
 * Copyright 2012 Facebook
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook;

import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import com.facebook.internal.Utility;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class SessionTests extends SessionTestsBase {

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        new SharedPreferencesTokenCachingStrategy(getActivity()).clear();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testFailNullArguments() {
        try {
            new Session(null);

            // Should not get here
            assertFalse(true);
        } catch (NullPointerException e) {
            // got expected exception
        }
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testActiveSessionChangeRegistration() {
        final WaitForBroadcastReceiver receiver0 = new WaitForBroadcastReceiver();
        final WaitForBroadcastReceiver receiver1 = new WaitForBroadcastReceiver();
        final WaitForBroadcastReceiver receiver2 = new WaitForBroadcastReceiver();
        final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getActivity());

        try {
            // Register these on the blocker thread so they will send
            // notifications there as well. The notifications need to be on a
            // different thread than the progress.
            Runnable initialize0 = new Runnable() {
                @Override
                public void run() {
                    broadcastManager.registerReceiver(receiver0, getActiveSessionAllFilter());

                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_SET));
                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_CLOSED));

                    broadcastManager.registerReceiver(receiver2,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                    broadcastManager.registerReceiver(receiver2,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_CLOSED));
                }
            };
            runOnBlockerThread(initialize0, true);

            // Verify all actions show up where they are expected
            WaitForBroadcastReceiver.incrementExpectCounts(receiver0, receiver1, receiver2);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiver0, receiver1, receiver2);

            WaitForBroadcastReceiver.incrementExpectCounts(receiver0, receiver1, receiver2);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_CLOSED);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiver0, receiver1, receiver2);

            WaitForBroadcastReceiver.incrementExpectCounts(receiver0, receiver1);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_SET);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiver0, receiver1);

            receiver0.incrementExpectCount();
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_UNSET);
            receiver0.waitForExpectedCalls();

            // Remove receiver1 and verify actions continue to show up where
            // expected
            broadcastManager.unregisterReceiver(receiver1);

            WaitForBroadcastReceiver.incrementExpectCounts(receiver0, receiver2);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiver0, receiver2);

            WaitForBroadcastReceiver.incrementExpectCounts(receiver0, receiver2);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_CLOSED);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiver0, receiver2);

            receiver0.incrementExpectCount();
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_SET);
            receiver0.waitForExpectedCalls();

            receiver0.incrementExpectCount();
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_UNSET);
            receiver0.waitForExpectedCalls();

            // Remove receiver0 and register receiver1 multiple times for one
            // action
            broadcastManager.unregisterReceiver(receiver0);

            Runnable initialize1 = new Runnable() {
                @Override
                public void run() {
                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                    broadcastManager.registerReceiver(receiver1,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                }
            };
            runOnBlockerThread(initialize1, true);

            receiver1.incrementExpectCount(3);
            receiver2.incrementExpectCount();
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);
            receiver1.waitForExpectedCalls();
            receiver2.waitForExpectedCalls();

            receiver2.incrementExpectCount();
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_CLOSED);
            receiver2.waitForExpectedCalls();

            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_SET);
            Session.postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_UNSET);

            closeBlockerAndAssertSuccess();
        } finally {
            broadcastManager.unregisterReceiver(receiver0);
            broadcastManager.unregisterReceiver(receiver1);
            broadcastManager.unregisterReceiver(receiver2);
            Session.setActiveSession(null);
        }
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testSetActiveSession() {
        Session.setActiveSession(null);

        final WaitForBroadcastReceiver receiverOpened = new WaitForBroadcastReceiver();
        final WaitForBroadcastReceiver receiverClosed = new WaitForBroadcastReceiver();
        final WaitForBroadcastReceiver receiverSet = new WaitForBroadcastReceiver();
        final WaitForBroadcastReceiver receiverUnset = new WaitForBroadcastReceiver();
        final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getActivity());

        try {
            Runnable initializeOnBlockerThread = new Runnable() {
                @Override
                public void run() {
                    broadcastManager.registerReceiver(receiverOpened,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_OPENED));
                    broadcastManager.registerReceiver(receiverClosed,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_CLOSED));
                    broadcastManager.registerReceiver(receiverSet,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_SET));
                    broadcastManager.registerReceiver(receiverUnset,
                            getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_UNSET));
                }
            };
            runOnBlockerThread(initializeOnBlockerThread, true);

            // null -> null should not fire events
            assertEquals(null, Session.getActiveSession());
            Session.setActiveSession(null);
            assertEquals(null, Session.getActiveSession());

            Session session0 = new Session.Builder(getActivity()).setApplicationId("FakeAppId")
                    .setTokenCachingStrategy(new MockTokenCachingStrategy()).build();
            assertEquals(SessionState.CREATED_TOKEN_LOADED, session0.getState());

            // For unopened session, we should only see the Set event.
            receiverSet.incrementExpectCount();
            Session.setActiveSession(session0);
            assertEquals(session0, Session.getActiveSession());
            receiverSet.waitForExpectedCalls();

            // When we open it, then we should see the Opened event.
            receiverOpened.incrementExpectCount();
            session0.openForRead(null);
            receiverOpened.waitForExpectedCalls();

            // Setting to itself should not fire events
            Session.setActiveSession(session0);
            assertEquals(session0, Session.getActiveSession());

            // Setting from one opened session to another should deliver a full
            // cycle of events
            WaitForBroadcastReceiver.incrementExpectCounts(receiverClosed, receiverUnset, receiverSet,
                    receiverOpened);
            Session session1 = new Session.Builder(getActivity()).setApplicationId("FakeAppId")
                    .setTokenCachingStrategy(new MockTokenCachingStrategy()).build();
            assertEquals(SessionState.CREATED_TOKEN_LOADED, session1.getState());
            session1.openForRead(null);
            assertEquals(SessionState.OPENED, session1.getState());
            Session.setActiveSession(session1);
            WaitForBroadcastReceiver.waitForExpectedCalls(receiverClosed, receiverUnset, receiverSet,
                    receiverOpened);
            assertEquals(SessionState.CLOSED, session0.getState());
            assertEquals(session1, Session.getActiveSession());

            closeBlockerAndAssertSuccess();
        } finally {
            broadcastManager.unregisterReceiver(receiverOpened);
            broadcastManager.unregisterReceiver(receiverClosed);
            broadcastManager.unregisterReceiver(receiverSet);
            broadcastManager.unregisterReceiver(receiverUnset);
            Session.setActiveSession(null);
        }
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenSuccess() {
        ArrayList<String> permissions = new ArrayList<String>();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);
        AccessToken openToken = AccessToken.createFromString("A token of thanks", permissions,
                AccessTokenSource.TEST_USER);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());

        session.addAuthorizeResult(openToken);
        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        verifySessionHasToken(session, openToken);

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Verify we saved the token to cache.
        assertTrue(cache.getSavedState() != null);
        assertEquals(openToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Verify token information is cleared.
        session.closeAndClearTokenInformation();
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenForPublishSuccess() {
        ArrayList<String> permissions = new ArrayList<String>();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);
        AccessToken openToken = AccessToken.createFromString("A token of thanks", permissions,
                AccessTokenSource.TEST_USER);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());

        session.addAuthorizeResult(openToken);
        session.openForPublish(new Session.OpenRequest(getActivity()).setCallback(statusRecorder)
                .setPermissions(Arrays.asList(new String[] { "publish_something", "manage_something",
                        "ads_management", "create_event", "rsvp_event" })));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        verifySessionHasToken(session, openToken);

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Verify we saved the token to cache.
        assertTrue(cache.getSavedState() != null);
        assertEquals(openToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Verify token information is cleared.
        session.closeAndClearTokenInformation();
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenForPublishSuccessWithReadPermissions() {
        ArrayList<String> permissions = new ArrayList<String>();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);
        AccessToken openToken = AccessToken.createFromString("A token of thanks", permissions,
                AccessTokenSource.TEST_USER);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());

        session.addAuthorizeResult(openToken);
        session.openForPublish(new Session.OpenRequest(getActivity()).setCallback(statusRecorder)
                .setPermissions(Arrays.asList(new String[] { "publish_something", "manage_something",
                        "ads_management", "create_event", "rsvp_event", "read_something" })));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        verifySessionHasToken(session, openToken);

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Verify we saved the token to cache.
        assertTrue(cache.getSavedState() != null);
        assertEquals(openToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Verify token information is cleared.
        session.closeAndClearTokenInformation();
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenFromTokenCache() {
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        String token = "A token less unique than most";
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(token, DEFAULT_TIMEOUT_MILLISECONDS);
        ScriptedSession session = createScriptedSessionOnBlockerThread("app-id", cache);

        // Verify state when we have a token in cache.
        assertEquals(SessionState.CREATED_TOKEN_LOADED, session.getState());

        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));

        // Verify we open with no authorize call.
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        // Verify no token information is saved.
        assertTrue(cache.getSavedState() == null);

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenActiveFromEmptyTokenCache() {
        new SharedPreferencesTokenCachingStrategy(getActivity()).clear();

        assertNull(Session.openActiveSessionFromCache(getActivity()));
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenFailure() {
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);
        Exception openException = new Exception();

        session.addAuthorizeResult(openException);
        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);

        // Verify we get the expected exception and no saved state.
        statusRecorder.waitForCall(session, SessionState.CLOSED_LOGIN_FAILED, openException);
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenForReadFailure() {
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        try {
            session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder)
                    .setPermissions(Arrays.asList(new String[] { "publish_something" })));
            fail("should not reach here without an exception");
        } catch (FacebookException e) {
            assertTrue(e.getMessage().contains("Cannot pass a publish or manage permission"));
        } finally {
            stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
            statusRecorder.close();
        }
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testRequestNewReadPermissionsSuccess() {
        ArrayList<String> permissions = new ArrayList<String>();
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        // Session.open
        final AccessToken openToken = AccessToken.createFromString("Allows playing outside", permissions,
                AccessTokenSource.TEST_USER);
        permissions.add("play_outside");

        session.addAuthorizeResult(openToken, "play_outside");
        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        verifySessionHasToken(session, openToken);
        assertTrue(cache.getSavedState() != null);
        assertEquals(openToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Successful Session.reauthorize with new permissions
        final AccessToken reauthorizeToken = AccessToken.createFromString(
                "Allows playing outside and eating ice cream", permissions, AccessTokenSource.TEST_USER);
        permissions.add("eat_ice_cream");

        session.addAuthorizeResult(reauthorizeToken, "play_outside", "eat_ice_cream");
        session.requestNewReadPermissions(new Session.NewPermissionsRequest(getActivity(), permissions));
        statusRecorder.waitForCall(session, SessionState.OPENED_TOKEN_UPDATED, null);

        verifySessionHasToken(session, reauthorizeToken);
        assertTrue(cache.getSavedState() != null);
        assertEquals(reauthorizeToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Failing reauthorization with new permissions
        final Exception reauthorizeException = new Exception("Don't run with scissors");
        permissions.add("run_with_scissors");

        session.addAuthorizeResult(reauthorizeException);
        session.requestNewReadPermissions(new Session.NewPermissionsRequest(getActivity(), permissions));
        statusRecorder.waitForCall(session, SessionState.OPENED_TOKEN_UPDATED, reauthorizeException);

        // Verify we do not overwrite cache if reauthorize fails
        assertTrue(cache.getSavedState() != null);
        assertEquals(reauthorizeToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorders.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testRequestNewPublishPermissionsSuccess() {
        ArrayList<String> permissions = new ArrayList<String>();
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        // Session.open
        final AccessToken openToken = AccessToken.createFromString("Allows playing outside", permissions,
                AccessTokenSource.TEST_USER);
        permissions.add("play_outside");

        session.addAuthorizeResult(openToken, "play_outside");
        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        verifySessionHasToken(session, openToken);
        assertTrue(cache.getSavedState() != null);
        assertEquals(openToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Successful Session.reauthorize with new permissions
        final AccessToken reauthorizeToken = AccessToken.createFromString(
                "Allows playing outside and publish eating ice cream", permissions, AccessTokenSource.TEST_USER);
        permissions.add("publish_eat_ice_cream");

        session.addAuthorizeResult(reauthorizeToken, "play_outside", "publish_eat_ice_cream");
        session.requestNewPublishPermissions(new Session.NewPermissionsRequest(getActivity(), permissions));
        statusRecorder.waitForCall(session, SessionState.OPENED_TOKEN_UPDATED, null);

        verifySessionHasToken(session, reauthorizeToken);
        assertTrue(cache.getSavedState() != null);
        assertEquals(reauthorizeToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Failing reauthorization with publish permissions on a read request
        permissions.add("publish_run_with_scissors");

        try {
            session.requestNewReadPermissions(new Session.NewPermissionsRequest(getActivity(), permissions));
            fail("Should not reach here without an exception");
        } catch (FacebookException e) {
            assertTrue(e.getMessage().contains("Cannot pass a publish or manage permission"));
        } finally {
            stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
            statusRecorder.close();
        }
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenWithAccessToken() {
        String token = "This is a fake token.";
        Date expirationDate = new Date(new Date().getTime() + 3600 * 1000);
        Date lastRefreshDate = new Date();
        List<String> permissions = Arrays.asList(new String[] { "email", "publish_stream" });

        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());

        AccessToken accessToken = AccessToken.createFromExistingAccessToken(token, expirationDate, lastRefreshDate,
                AccessTokenSource.FACEBOOK_APPLICATION_WEB, permissions);
        session.open(accessToken, statusRecorder);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        AccessToken expectedToken = new AccessToken(token, expirationDate, permissions,
                AccessTokenSource.FACEBOOK_APPLICATION_WEB, lastRefreshDate);
        verifySessionHasToken(session, expectedToken);

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Verify we saved the token to cache.
        assertTrue(cache.getSavedState() != null);
        assertEquals(expectedToken.getToken(), TokenCachingStrategy.getToken(cache.getSavedState()));

        // Verify token information is cleared.
        session.closeAndClearTokenInformation();
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenWithAccessTokenWithDefaults() {
        String token = "This is a fake token.";

        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());

        AccessToken accessToken = AccessToken.createFromExistingAccessToken(token, null, null, null, null);
        session.open(accessToken, statusRecorder);
        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        assertEquals(token, session.getAccessToken());
        assertEquals(new Date(Long.MAX_VALUE), session.getExpirationDate());
        assertEquals(0, session.getPermissions().size());

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Verify we saved the token to cache.
        assertTrue(cache.getSavedState() != null);

        // Verify token information is cleared.
        session.closeAndClearTokenInformation();
        assertTrue(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @MediumTest
    @LargeTest
    public void testSessionWillExtendTokenIfNeeded() {
        TestSession session = openTestSessionWithSharedUser();
        session.forceExtendAccessToken(true);

        Request request = Request.newMeRequest(session, null);
        request.executeAndWait();

        assertTrue(session.getWasAskedToExtendAccessToken());
    }

    @MediumTest
    @LargeTest
    public void testSessionWillNotExtendTokenIfCurrentlyAttempting() {
        TestSession session = openTestSessionWithSharedUser();
        session.forceExtendAccessToken(true);
        session.fakeTokenRefreshAttempt();

        Request request = Request.newMeRequest(session, null);
        request.executeAndWait();
        assertFalse(session.getWasAskedToExtendAccessToken());
    }

    @LargeTest
    public void testBasicSerialization() throws IOException, ClassNotFoundException {
        // Try to test the happy path, that there are no unserializable fields
        // in the session.
        Session session0 = new Session.Builder(getActivity()).setApplicationId("fakeID").build();
        Session session1 = TestUtils.serializeAndUnserialize(session0);

        // do some basic assertions
        assertNotNull(session0.getAccessToken());
        assertEquals(session0, session1);

        Session.AuthorizationRequest authRequest0 = new Session.OpenRequest(getActivity()).setRequestCode(123)
                .setLoginBehavior(SessionLoginBehavior.SSO_ONLY);
        Session.AuthorizationRequest authRequest1 = TestUtils.serializeAndUnserialize(authRequest0);

        assertEquals(authRequest0.getLoginBehavior(), authRequest1.getLoginBehavior());
        assertEquals(authRequest0.getRequestCode(), authRequest1.getRequestCode());
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenSessionWithNativeLinkingIntent() {
        String token = "A token less unique than most";

        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.putExtras(getNativeLinkingExtras(token));

        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, DEFAULT_TIMEOUT_MILLISECONDS);
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);

        assertEquals(SessionState.CREATED, session.getState());

        AccessToken accessToken = AccessToken.createFromNativeLinkingIntent(intent);
        assertNotNull(accessToken);
        session.open(accessToken, statusRecorder);

        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        assertEquals(token, session.getAccessToken());
        // Expiration time should be 3600s after now (allow 5s variation for test execution time)
        long delta = session.getExpirationDate().getTime() - new Date().getTime();
        assertTrue(Math.abs(delta - 3600 * 1000) < 5000);
        assertEquals(0, session.getPermissions().size());
        assertEquals(Utility.getMetadataApplicationId(getActivity()), session.getApplicationId());

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        assertFalse(cache.getSavedState() == null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpenActiveSessionWithNativeLinkingIntent() {
        Session activeSession = Session.getActiveSession();
        if (activeSession != null) {
            activeSession.closeAndClearTokenInformation();
        }

        SharedPreferencesTokenCachingStrategy tokenCache = new SharedPreferencesTokenCachingStrategy(getActivity());
        assertEquals(0, tokenCache.load().size());

        String token = "A token less unique than most";

        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.putExtras(getNativeLinkingExtras(token));

        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();

        AccessToken accessToken = AccessToken.createFromNativeLinkingIntent(intent);
        assertNotNull(accessToken);
        Session session = Session.openActiveSessionWithAccessToken(getActivity(), accessToken, statusRecorder);
        assertEquals(session, Session.getActiveSession());

        statusRecorder.waitForCall(session, SessionState.OPENED, null);

        assertNotSame(0, tokenCache.load().size());

        assertEquals(token, session.getAccessToken());
        // Expiration time should be 3600s after now (allow 5s variation for test execution time)
        long delta = session.getExpirationDate().getTime() - new Date().getTime();
        assertTrue(Math.abs(delta - 3600 * 1000) < 5000);
        assertEquals(0, session.getPermissions().size());
        assertEquals(Utility.getMetadataApplicationId(getActivity()), session.getApplicationId());

        // Verify we get a close callback.
        session.close();
        statusRecorder.waitForCall(session, SessionState.CLOSED, null);

        // Wait a bit so we can fail if any unexpected calls arrive on the
        // recorder.
        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    @SmallTest
    @MediumTest
    @LargeTest
    public void testOpeningSessionWithPendingRequestResultsInExceptionCallback() {
        ArrayList<String> permissions = new ArrayList<String>();
        MockTokenCachingStrategy cache = new MockTokenCachingStrategy(null, 0);
        SessionStatusCallbackRecorder statusRecorder = new SessionStatusCallbackRecorder();
        ScriptedSession session = createScriptedSessionOnBlockerThread(cache);
        AccessToken openToken = AccessToken.createFromString("A token of thanks", permissions,
                AccessTokenSource.TEST_USER);

        // Verify state with no token in cache
        assertEquals(SessionState.CREATED, session.getState());
        session.addPendingAuthorizeResult();

        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        session.openForRead(new Session.OpenRequest(getActivity()).setCallback(statusRecorder));
        statusRecorder.waitForCall(session, SessionState.OPENING, null);
        statusRecorder.waitForCall(session, SessionState.OPENING, new UnsupportedOperationException());

        stall(STRAY_CALLBACK_WAIT_MILLISECONDS);
        statusRecorder.close();
    }

    static IntentFilter getActiveSessionFilter(String... actions) {
        IntentFilter filter = new IntentFilter();

        for (String action : actions) {
            filter.addAction(action);
        }

        return filter;
    }

    static IntentFilter getActiveSessionAllFilter() {
        return getActiveSessionFilter(Session.ACTION_ACTIVE_SESSION_CLOSED, Session.ACTION_ACTIVE_SESSION_OPENED,
                Session.ACTION_ACTIVE_SESSION_SET, Session.ACTION_ACTIVE_SESSION_UNSET);
    }

    private void verifySessionHasToken(Session session, AccessToken token) {
        assertEquals(token.getToken(), session.getAccessToken());
        assertEquals(token.getExpires(), session.getExpirationDate());
        TestUtils.assertAtLeastExpectedPermissions(token.getPermissions(), session.getPermissions());
    }
}