package org.commcare.models.database; import org.commcare.CommCareApplication; import org.commcare.CommCareTestApplication; import org.commcare.android.CommCareTestRunner; import org.commcare.android.util.TestAppInstaller; import org.commcare.data.xml.DataModelPullParser; import org.commcare.xml.AndroidTransactionParserFactory; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.services.storage.EntityFilter; import org.javarosa.core.services.storage.IStorageIterator; import org.javarosa.core.services.storage.IStorageUtilityIndexed; import org.javarosa.xml.util.InvalidStructureException; import org.javarosa.xml.util.UnfullfilledRequirementsException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Vector; /** * Test file-backed sql storage currently used to store fixtures, which can get * large. File-backed storage can store encrypted or unencrypted files. * * @author Phillip Mates (pmates@dimagi.com). */ @Config(application = CommCareTestApplication.class) @RunWith(CommCareTestRunner.class) public class StoreFixturesOnFilesystemTests { private AndroidSandbox sandbox; @Before public void setup() { UnencryptedHybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); HybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); sandbox = installAppWithFixtureData(this.getClass(), "odk_level_ipm_restore.xml"); } public static AndroidSandbox installAppWithFixtureData(Class testClass, String fixtureResource) { TestAppInstaller.installAppAndLogin( "jr://resource/commcare-apps/archive_form_tests/profile.ccpr", "test", "123"); AndroidSandbox sandbox = new AndroidSandbox(CommCareApplication.instance()); try { parseIntoSandbox(testClass.getClassLoader().getResourceAsStream(fixtureResource), false); } catch (Exception e) { e.printStackTrace(); } return sandbox; } public static void parseIntoSandbox(InputStream stream, boolean failfast) throws InvalidStructureException, IOException, UnfullfilledRequirementsException, XmlPullParserException { AndroidTransactionParserFactory factory = new AndroidTransactionParserFactory(CommCareApplication.instance().getApplicationContext(), null); DataModelPullParser parser = new DataModelPullParser(stream, factory, failfast, true); parser.parse(); } /** * User level fixtures are encrypted. To do so, they are stored in * encrypted files and the key is stored in the encrypted databse. This * test ensures that the file is actually encrypted by trying to * deserialize the contents of a fixture file w/o decrypting the file * first. */ @Test public void testStoredEncrypted() { IStorageUtilityIndexed<FormInstance> userFixtureStorage = sandbox.getUserFixtureStorage(); File dbDir = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getDbDirForTesting(); File[] serializedFixtureFiles = dbDir.listFiles(); Assert.assertTrue(serializedFixtureFiles.length > 0); try { ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage) .newObject(new FileInputStream(serializedFixtureFiles[0]), -1); } catch (FileNotFoundException e) { Assert.fail("Unable to find db storage file that should exist"); } catch (RuntimeException e) { // we expect to fail here because the stream wasn't decrypted } catch (Exception e) { Assert.fail("Should have failed with a runtime exception when trying to deserialize an encrypted object"); } } /** * App level fixtures are stored un-encrypted. To do so, they are stored in * plain-text files. This test ensures that by trying to deserialize the * contents of one of those files. */ @Test public void testStoredUnencrypted() { IStorageUtilityIndexed<FormInstance> appFixtureStorage = sandbox.getAppFixtureStorage(); File dbDir = ((HybridFileBackedSqlStorage<FormInstance>)appFixtureStorage).getDbDirForTesting(); File[] serializedFixtureFiles = dbDir.listFiles(); Assert.assertTrue(serializedFixtureFiles.length > 0); try { ((UnencryptedHybridFileBackedSqlStorage<FormInstance>)appFixtureStorage) .newObject(new FileInputStream(serializedFixtureFiles[0]), -1); } catch (Exception e) { Assert.fail("Should be able to deserialize an unencrypted object"); } } @Test public void testRemoveAllDeletesFiles() { IStorageUtilityIndexed<FormInstance> userFixtureStorage = sandbox.getUserFixtureStorage(); File dbDir = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getDbDirForTesting(); ArrayList<File> removedFiles = new ArrayList<>(); for (IStorageIterator i = userFixtureStorage.iterate(); i.hasMore(); ) { File fixtureFile = new File(((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage) .getEntryFilenameForTesting(i.nextID())); removedFiles.add(fixtureFile); } userFixtureStorage.removeAll(); for (File fixtureFile : removedFiles) { Assert.assertTrue(!fixtureFile.exists()); } Assert.assertTrue(!dbDir.exists()); } @Test public void testRemoveEntityFilterDeleteFiles() { IStorageUtilityIndexed<FormInstance> userFixtureStorage = sandbox.getUserFixtureStorage(); File dbDir = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getDbDirForTesting(); int fileCountBefore = dbDir.listFiles().length; userFixtureStorage.removeAll(new EntityFilter<FormInstance>() { @Override public boolean matches(FormInstance fixture) { return "commtrack:products".equals(fixture.getRoot().getInstanceName()); } }); int fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfter == 1); // make sure we can read all the existing records just fine for (IStorageIterator i = userFixtureStorage.iterate(); i.hasMore(); ) { i.nextRecord(); } } @Test public void testRemoveDeletesFiles() { IStorageUtilityIndexed<FormInstance> userFixtureStorage = sandbox.getUserFixtureStorage(); File dbDir = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getDbDirForTesting(); File[] serializedFixtureFiles = dbDir.listFiles(); Assert.assertTrue(serializedFixtureFiles.length > 0); int count = 0; int idOne = -1; for (IStorageIterator i = userFixtureStorage.iterate(); i.hasMore(); ) { if (count == 0) { removeOneEntry(i.nextID(), userFixtureStorage); } else if (count == 1) { idOne = i.nextID(); } else if (count == 2) { removeTwoEntries(idOne, i.nextID(), userFixtureStorage); } else { // seems to be required; otherwise iterator loops forever. Not // sure if it is a robolectric bug or a bug in our iterator // that comes up when we iterate and delete at the same time break; } count++; } } private void removeOneEntry(int id, IStorageUtilityIndexed<FormInstance> userFixtureStorage) { String fixtureFilename = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getEntryFilenameForTesting(id); File fixtureFile = new File(fixtureFilename); Assert.assertTrue(fixtureFile.exists()); userFixtureStorage.remove(id); Assert.assertTrue(!fixtureFile.exists()); } private void removeTwoEntries(int idOne, int idTwo, IStorageUtilityIndexed<FormInstance> userFixtureStorage) { ArrayList<Integer> toRemoveList = new ArrayList<>(); toRemoveList.add(idOne); toRemoveList.add(idTwo); String fixtureOneFilename = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getEntryFilenameForTesting(idOne); String fixtureTwoFilename = ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).getEntryFilenameForTesting(idTwo); File fixtureFileOne = new File(fixtureOneFilename); File fixtureFileTwo = new File(fixtureTwoFilename); Assert.assertTrue(fixtureFileOne.exists()); Assert.assertTrue(fixtureFileTwo.exists()); ((HybridFileBackedSqlStorage<FormInstance>)userFixtureStorage).remove(toRemoveList); Assert.assertTrue(!fixtureFileOne.exists()); Assert.assertTrue(!fixtureFileTwo.exists()); } @Test public void testUpdate() { // test encrypted update HybridFileBackedSqlStorage<FormInstance> userFixtureStorage = CommCareApplication.instance().getFileBackedUserStorage("fixture", FormInstance.class); FormInstance form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); String newName = "new_name"; form.setName(newName); userFixtureStorage.update(form.getID(), form); form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); Assert.assertEquals(newName, form.getName()); // test unencrypted update UnencryptedHybridFileBackedSqlStorage<FormInstance> appFixtureStorage = CommCareApplication.instance().getCurrentApp().getFileBackedStorage("fixture", FormInstance.class); form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); form.setName(newName); appFixtureStorage.update(form.getID(), form); form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); Assert.assertEquals(newName, form.getName()); } @Test public void testRecordLookup() { // test encrypted record lookup HybridFileBackedSqlStorage<FormInstance> userFixtureStorage = CommCareApplication.instance().getFileBackedUserStorage("fixture", FormInstance.class); Vector<FormInstance> forms = userFixtureStorage.getRecordsForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); Assert.assertTrue(forms.size() == 1); FormInstance form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); Assert.assertEquals(forms.firstElement().getRoot(), form.getRoot()); form = userFixtureStorage.getRecordForValue(FormInstance.META_ID, "commtrack:programs"); Assert.assertEquals(forms.firstElement().getRoot(), form.getRoot()); // Test unencrpyted record lookup UnencryptedHybridFileBackedSqlStorage<FormInstance> appFixtureStorage = CommCareApplication.instance().getCurrentApp().getFileBackedStorage("fixture", FormInstance.class); forms = appFixtureStorage.getRecordsForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); Assert.assertTrue(forms.size() == 1); form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); Assert.assertEquals(forms.firstElement().getRoot(), form.getRoot()); form = appFixtureStorage.getRecordForValue(FormInstance.META_ID, "user-groups"); Assert.assertEquals(forms.firstElement().getRoot(), form.getRoot()); } }