/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ package org.mozilla.gecko.sync.setup.test; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.concurrent.TimeUnit; import org.mozilla.android.sync.test.AndroidSyncTestCase; import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.GlobalConstants; import org.mozilla.gecko.sync.SyncConstants; import org.mozilla.gecko.sync.SyncConfiguration; import org.mozilla.gecko.sync.Utils; import org.mozilla.gecko.sync.config.AccountPickler; import org.mozilla.gecko.sync.setup.Constants; import org.mozilla.gecko.sync.setup.SyncAccounts; import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.test.InstrumentationTestCase; /** * We can use <code>performWait</code> and <code>performNotify</code> here if we * are careful about threading issues with <code>AsyncTask</code>. We need to * take some care to both create and run certain tasks on the main thread -- * moving the object allocation out of the UI thread causes failures! * <p> * @see "<a href='http://stackoverflow.com/questions/2321829/android-asynctask-testing-problem-with-android-test-framework'> * http://stackoverflow.com/questions/2321829/android-asynctask-testing-problem-with-android-test-framework</a>." */ public class TestSyncAccounts extends AndroidSyncTestCase { private static final String TEST_USERNAME = "testAccount@mozilla.com"; private static final String TEST_SYNCKEY = "testSyncKey"; private static final String TEST_PASSWORD = "testPassword"; private static final String TEST_SERVERURL = "test.server.url/"; private static final String TEST_CLUSTERURL = "test.cluster.url/"; public static final String TEST_ACCOUNTTYPE = SyncConstants.ACCOUNTTYPE_SYNC; public static final String TEST_PRODUCT = GlobalConstants.BROWSER_INTENT_PACKAGE; public static final String TEST_PROFILE = Constants.DEFAULT_PROFILE; public static final long TEST_VERSION = SyncConfiguration.CURRENT_PREFS_VERSION; public static final String TEST_PREFERENCE = "testPreference"; public static final String TEST_SYNC_ID = "testSyncID"; private Account account; private Context context; private AccountManager accountManager; private SyncAccountParameters syncAccount; public void setUp() { account = null; context = getApplicationContext(); accountManager = AccountManager.get(context); syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, null); } public static void deleteAccount(final InstrumentationTestCase test, final AccountManager accountManager, final Account account) { performWait(new Runnable() { @Override public void run() { try { test.runTestOnUiThread(new Runnable() { final AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() { @Override public void run(AccountManagerFuture<Boolean> future) { try { future.getResult(5L, TimeUnit.SECONDS); } catch (Exception e) { } performNotify(); } }; @Override public void run() { accountManager.removeAccount(account, callback, null); } }); } catch (Throwable e) { performNotify(e); } } }); } public void tearDown() { if (account == null) { return; } deleteAccount(this, accountManager, account); account = null; } public void testSyncAccountParameters() { assertEquals(TEST_USERNAME, syncAccount.username); assertNull(syncAccount.accountManager); assertNull(syncAccount.serverURL); try { syncAccount = new SyncAccountParameters(context, null, null, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL); } catch (IllegalArgumentException e) { return; } catch (Exception e) { fail("Did not expect exception: " + e); } fail("Expected IllegalArgumentException."); } public void testCreateAccount() { int before = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; account = SyncAccounts.createSyncAccount(syncAccount, false); int afterCreate = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterCreate > before); deleteAccount(this, accountManager, account); account = null; int afterDelete = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertEquals(before, afterDelete); } public void testCreateSecondAccount() { int before = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; account = SyncAccounts.createSyncAccount(syncAccount, false); int afterFirst = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterFirst > before); SyncAccountParameters secondSyncAccount = new SyncAccountParameters(context, null, "second@username.com", TEST_SYNCKEY, TEST_PASSWORD, null); Account second = SyncAccounts.createSyncAccount(secondSyncAccount, false); assertNotNull(second); int afterSecond = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterSecond > afterFirst); deleteAccount(this, accountManager, second); deleteAccount(this, accountManager, account); account = null; int afterDelete = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertEquals(before, afterDelete); } public void testCreateDuplicateAccount() { int before = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; account = SyncAccounts.createSyncAccount(syncAccount, false); int afterCreate = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterCreate > before); Account dupe = SyncAccounts.createSyncAccount(syncAccount, false); assertNull(dupe); } public Boolean result; protected boolean doAccountsExistTask() { final TestSyncAccounts self = this; performWait(new Runnable() { @Override public void run() { try { runTestOnUiThread(new Runnable() { @Override public void run() { final SyncAccounts.AccountsExistTask task = new SyncAccounts.AccountsExistTask() { @Override public void onPostExecute(Boolean result) { self.result = result; performNotify(); } }; task.execute(context); } }); } catch (Throwable e) { performNotify(e); } } }); assertNotNull(result); return result.booleanValue(); } public void testAccountsExistTask() { int before = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; account = SyncAccounts.createSyncAccount(syncAccount, false); int afterCreate = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterCreate > before); assertTrue(doAccountsExistTask()); deleteAccount(this, accountManager, account); account = null; int afterDelete = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertEquals(before, afterDelete); } public void testAccountsExistTaskAndPickling() { // Make sure there's nothing to un-pickle. context.deleteFile(Constants.ACCOUNT_PICKLE_FILENAME); // We would love to make this a proper test by starting with 0 accounts and // then un-pickling 1 account, but then our test suite doesn't play nicely // with existing accounts. Instead, we're going to assume that testers will // have/not have an existing Account frequently enough to make this // worthwhile. // assertFalse(doAccountsExistTask()); // Write pickle file, and ensure it gets un-pickled. final SyncAccountParameters params = new SyncAccountParameters(getApplicationContext(), null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL, null, "testClientName", "testClientGuid"); try { AccountPickler.pickle(context, Constants.ACCOUNT_PICKLE_FILENAME, params, false); assertTrue(doAccountsExistTask()); } finally { // Clean up! for (Account account : accountManager.getAccountsByType(TEST_ACCOUNTTYPE)) { if (TEST_USERNAME.equals(account.name)) { TestSyncAccounts.deleteAccount(this, accountManager, account); } } } } public void testCreateAccountTask() { int before = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; final TestSyncAccounts self = this; performWait(new Runnable() { @Override public void run() { try { runTestOnUiThread(new Runnable() { @Override public void run() { final SyncAccounts.CreateSyncAccountTask task = new SyncAccounts.CreateSyncAccountTask(false) { @Override public void onPostExecute(Account account) { self.account = account; performNotify(); } }; task.execute(syncAccount); } }); } catch (Throwable e) { performNotify(e); } } }); assertNotNull(account); int afterCreate = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertTrue(afterCreate > before); deleteAccount(this, accountManager, account); account = null; int afterDelete = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length; assertEquals(before, afterDelete); } public void testClientRecord() throws NoSuchAlgorithmException, UnsupportedEncodingException { final String TEST_NAME = "testName"; final String TEST_GUID = "testGuid"; syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, null, null, TEST_NAME, TEST_GUID); account = SyncAccounts.createSyncAccount(syncAccount, false); assertNotNull(account); SharedPreferences prefs = Utils.getSharedPreferences(context, TEST_PRODUCT, TEST_USERNAME, SyncAccounts.DEFAULT_SERVER, TEST_PROFILE, TEST_VERSION); // Verify that client record is set. assertEquals(TEST_GUID, prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null)); assertEquals(TEST_NAME, prefs.getString(SyncConfiguration.PREF_CLIENT_NAME, null)); // Let's verify that clusterURL is correctly not set. String clusterURL = prefs.getString(SyncConfiguration.PREF_CLUSTER_URL, null); assertNull(clusterURL); } public void testClusterURL() throws NoSuchAlgorithmException, UnsupportedEncodingException { syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL, TEST_CLUSTERURL, null, null); account = SyncAccounts.createSyncAccount(syncAccount, false); assertNotNull(account); SharedPreferences prefs = Utils.getSharedPreferences(context, TEST_PRODUCT, TEST_USERNAME, TEST_SERVERURL, TEST_PROFILE, TEST_VERSION); String clusterURL = prefs.getString(SyncConfiguration.PREF_CLUSTER_URL, null); assertNotNull(clusterURL); assertEquals(TEST_CLUSTERURL, clusterURL); // Let's verify that client name and GUID are not set. assertNull(prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null)); assertNull(prefs.getString(SyncConfiguration.PREF_CLIENT_NAME, null)); } /** * Verify that creating an account wipes stale settings in Shared Preferences. */ public void testCreatingWipesSharedPrefs() throws Exception { final String TEST_PREFERENCE = "testPreference"; final String TEST_SYNC_ID = "testSyncID"; SharedPreferences prefs = Utils.getSharedPreferences(context, TEST_PRODUCT, TEST_USERNAME, TEST_SERVERURL, TEST_PROFILE, TEST_VERSION); prefs.edit().putString(SyncConfiguration.PREF_SYNC_ID, TEST_SYNC_ID).commit(); prefs.edit().putString(TEST_PREFERENCE, TEST_SYNC_ID).commit(); syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL); account = SyncAccounts.createSyncAccount(syncAccount, false); assertNotNull(account); // All values deleted (known and unknown). assertNull(prefs.getString(SyncConfiguration.PREF_SYNC_ID, null)); assertNull(prefs.getString(TEST_SYNC_ID, null)); } /** * Verify that creating an account preserves settings in Shared Preferences when asked. */ public void testCreateSyncAccountWithExistingPreferences() throws Exception { SharedPreferences prefs = Utils.getSharedPreferences(context, TEST_PRODUCT, TEST_USERNAME, TEST_SERVERURL, TEST_PROFILE, TEST_VERSION); prefs.edit().putString(SyncConfiguration.PREF_SYNC_ID, TEST_SYNC_ID).commit(); prefs.edit().putString(TEST_PREFERENCE, TEST_SYNC_ID).commit(); assertNotNull(prefs.getString(TEST_PREFERENCE, null)); assertNotNull(prefs.getString(SyncConfiguration.PREF_SYNC_ID, null)); syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL); account = SyncAccounts.createSyncAccountPreservingExistingPreferences(syncAccount, false); assertNotNull(account); // All values remain (known and unknown). assertNotNull(prefs.getString(TEST_PREFERENCE, null)); assertNotNull(prefs.getString(SyncConfiguration.PREF_SYNC_ID, null)); } protected void assertParams(final SyncAccountParameters params) throws Exception { assertNotNull(params); assertEquals(context, params.context); assertEquals(Utils.usernameFromAccount(TEST_USERNAME), params.username); assertEquals(TEST_PASSWORD, params.password); assertEquals(TEST_SERVERURL, params.serverURL); assertEquals(TEST_SYNCKEY, params.syncKey); } public void testBlockingFromAndroidAccountV0() throws Throwable { syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL, TEST_CLUSTERURL, null, null); try { account = SyncAccounts.createSyncAccount(syncAccount); assertNotNull(account); // Test fetching parameters multiple times. Historically, we needed to // invalidate this token type every fetch; now we don't, but we'd like // to ensure multiple fetches work. SyncAccountParameters params = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account); assertParams(params); params = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account); assertParams(params); // Test this works on the main thread. this.runTestOnUiThread(new Runnable() { @Override public void run() { SyncAccountParameters params; try { params = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account); assertParams(params); } catch (Exception e) { fail("Fetching Sync account parameters failed on UI thread."); } } }); } finally { if (account != null) { deleteAccount(this, accountManager, account); account = null; } } } public void testMakeSyncAccountDeletedIntent() throws Throwable { syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL, TEST_CLUSTERURL, null, null); try { account = SyncAccounts.createSyncAccount(syncAccount); assertNotNull(account); Intent intent = SyncAccounts.makeSyncAccountDeletedIntent(context, accountManager, account); assertEquals(SyncConstants.SYNC_ACCOUNT_DELETED_ACTION, intent.getAction()); assertEquals(SyncConstants.SYNC_ACCOUNT_DELETED_INTENT_VERSION, intent.getLongExtra(Constants.JSON_KEY_VERSION, 0)); assertEquals(TEST_USERNAME, intent.getStringExtra(Constants.JSON_KEY_ACCOUNT)); assertTrue(Math.abs(intent.getLongExtra(Constants.JSON_KEY_TIMESTAMP, 0) - System.currentTimeMillis()) < 1000); String payload = intent.getStringExtra(Constants.JSON_KEY_PAYLOAD); assertNotNull(payload); SyncAccountParameters params = new SyncAccountParameters(context, accountManager, ExtendedJSONObject.parseJSONObject(payload)); // Can't use assertParams because Sync key is deleted. assertNotNull(params); assertEquals(context, params.context); assertEquals(Utils.usernameFromAccount(TEST_USERNAME), params.username); assertEquals(TEST_PASSWORD, params.password); assertEquals(TEST_SERVERURL, params.serverURL); assertEquals("", params.syncKey); } finally { if (account != null) { deleteAccount(this, accountManager, account); account = null; } } } public void testBlockingPrefsFromAndroidAccountV0() throws Exception { // Create test account with prefs. SharedPreferences prefs = Utils.getSharedPreferences(context, TEST_PRODUCT, TEST_USERNAME, TEST_SERVERURL, TEST_PROFILE, TEST_VERSION); prefs.edit().putString(TEST_PREFERENCE, TEST_SYNC_ID).commit(); syncAccount = new SyncAccountParameters(context, null, TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVERURL); account = SyncAccounts.createSyncAccountPreservingExistingPreferences(syncAccount, false); assertNotNull(account); // Fetch account and check prefs. SharedPreferences sharedPreferences = SyncAccounts.blockingPrefsFromAndroidAccountV0(context, accountManager, account, TEST_PRODUCT, TEST_PROFILE, TEST_VERSION); assertNotNull(sharedPreferences); assertEquals(TEST_SYNC_ID, sharedPreferences.getString(TEST_PREFERENCE, null)); } }