package org.commcare.android.tests.processing; import android.database.Cursor; import org.commcare.CommCareApp; import org.commcare.CommCareApplication; import org.commcare.CommCareTestApplication; import org.commcare.ManageKeyRecordTaskFake; import org.commcare.activities.DataPullControllerMock; import org.commcare.activities.LoginMode; import org.commcare.android.CommCareTestRunner; import org.commcare.android.database.app.models.UserKeyRecord; import org.commcare.android.database.user.models.FormRecord; import org.commcare.android.tests.activities.FormRecordListActivityTest; import org.commcare.android.util.SavedFormLoader; import org.commcare.android.util.TestAppInstaller; import org.commcare.android.util.TestUtils; import org.commcare.models.database.SqlStorage; import org.commcare.provider.InstanceProviderAPI; import org.commcare.tasks.templates.CommCareTaskConnector; import org.commcare.views.notifications.MessageTag; import org.commcare.views.notifications.NotificationMessageFactory; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.Date; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Test various key record setup code paths * * @author Phillip Mates (pmates@dimagi.com) */ @Config(application = CommCareTestApplication.class) @RunWith(CommCareTestRunner.class) public class KeyRecordTest { private CommCareApp app; @Before public void setupTests() { TestUtils.initializeStaticTestStorage(); TestAppInstaller.installApp("jr://resource/commcare-apps/form_nav_tests/profile.ccpr"); app = CommCareApplication.instance().getCurrentApp(); } /** * Test key record pull attempt where the xml payload doesn't have a key in * it. */ @Test public void invalidXMLKeyRecordResponseTest() { runKeyRecordTask("old_pass", "/inputs/empty_key_record.xml", NotificationMessageFactory.StockMessages.Remote_BadRestore); SqlStorage<UserKeyRecord> recordStorage = app.getStorage(UserKeyRecord.class); assertEquals(0, recordStorage.getNumRecords()); } /** * Check that old sandbox is completely trashed if a new key record, w/ new * password and sandbox id, is sent down. */ @Test public void keyRecordWithDifferentSandboxIdTest() { runKeyRecordTask("old_pass", "/inputs/key_record_create.xml"); SqlStorage<UserKeyRecord> recordStorage = app.getStorage(UserKeyRecord.class); assertEquals(1, recordStorage.getNumRecords()); runKeyRecordTask("new_pass", "/inputs/key_record_create_different_uuid.xml"); assertActiveKeyRecordCount(1, recordStorage); } /** * If HQ sends down a key record for the same password, but with a new * sandbox ID, data needs to be migrated from the old sandbox to the new * one. * * Not positive, but I suspect this happens when the old key expires on the server. */ @Test public void keyRecordMigration() { runKeyRecordTask("old_pass", "/inputs/key_record_create.xml"); SqlStorage<UserKeyRecord> recordStorage = app.getStorage(UserKeyRecord.class); assertEquals(1, recordStorage.getNumRecords()); TestAppInstaller.login("test", "old_pass"); SavedFormLoader.loadFormsFromPayload( "/commcare-apps/form_nav_tests/form_instances_restore.xml", FormRecord.STATUS_SAVED); SqlStorage<FormRecord> formRecordStorage = CommCareApplication.instance().getUserStorage(FormRecord.class); assertEquals(2, formRecordStorage.getNumRecords()); assertFormInstanceCount(2); CommCareApplication.instance().closeUserSession(); markOutOfDate(recordStorage); runKeyRecordTask("old_pass", "/inputs/key_record_create_different_uuid.xml"); TestAppInstaller.login("test", "old_pass"); assertActiveKeyRecordCount(1, recordStorage); CommCareApplication.instance().closeUserSession(); // trigger form record cleanup. runKeyRecordTask("old_pass", "/inputs/key_record_create_different_uuid.xml"); TestAppInstaller.login("test", "old_pass"); formRecordStorage = CommCareApplication.instance().getUserStorage(FormRecord.class); assertEquals(2, formRecordStorage.getNumRecords()); assertFormInstanceCount(2); testOpeningMigratedForm(); } private static void assertFormInstanceCount(int expectedCount) { Cursor c = RuntimeEnvironment.application.getContentResolver().query(InstanceProviderAPI.InstanceColumns.CONTENT_URI, null, null, null, null); if (c == null) { fail("Query returned 'null' when we expected to find " + expectedCount + " instances"); } else { assertEquals(expectedCount, c.getCount()); } c.close(); } private static void testOpeningMigratedForm() { TestAppInstaller.login("test", "old_pass"); FormRecordListActivityTest.openASavedForm(2, 1); } private static void assertActiveKeyRecordCount(int expectedCount, SqlStorage<UserKeyRecord> recordStorage) { int activeCount = 0; for (UserKeyRecord record : recordStorage) { if (record.isActive()) { activeCount++; } } assertEquals(expectedCount, activeCount); } private void runKeyRecordTask(String password, String keyXmlFile) { runKeyRecordTask(password, keyXmlFile, null); } private void runKeyRecordTask(String password, String keyXmlFile, MessageTag expectedMessage) { ManageKeyRecordTaskFake keyRecordTast = new ManageKeyRecordTaskFake(RuntimeEnvironment.application, 1, "test", password, LoginMode.PASSWORD, app, false, false, keyXmlFile); keyRecordTast.connect((CommCareTaskConnector)new DataPullControllerMock(expectedMessage)); keyRecordTast.execute(); Robolectric.flushBackgroundThreadScheduler(); Robolectric.flushForegroundThreadScheduler(); } private static void markOutOfDate(SqlStorage<UserKeyRecord> recordStorage) { Date lastWeek = (new DateTime()).minusWeeks(1).toDate(); Date yesterday = (new DateTime()).minusDays(1).toDate(); UserKeyRecord ukr = recordStorage.getRecordForValue(UserKeyRecord.META_USERNAME, "test"); UserKeyRecord outOfDateRecord = new UserKeyRecord(ukr.getUsername(), ukr.getPasswordHash(), ukr.getEncryptedKey(), ukr.getWrappedPassword(), lastWeek, yesterday, ukr.getUuid(), ukr.getType()); outOfDateRecord.setID(ukr.getID()); recordStorage.write(outOfDateRecord); } }