/* 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));
}
}