package org.commcare.models.database; import org.commcare.CommCareApp; import org.commcare.CommCareApplication; import org.commcare.CommCareTestApplication; import org.commcare.android.CommCareTestRunner; import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.model.instance.FormInstanceWithFailures; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import java.io.File; /** * Test hybrid storage update logic that moves object from db to fs, or vice-versa, * based on object update size * * @author Phillip Mates (pmates@dimagi.com). */ @Config(application = CommCareTestApplication.class) @RunWith(CommCareTestRunner.class) public class HybridFileBackedSqlStorageTest { @Before public void setup() { UnencryptedHybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); HybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); StoreFixturesOnFilesystemTests.installAppWithFixtureData(this.getClass(), "odk_level_ipm_restore.xml"); } /** * Write an object to the filesystem but fail before finializing the * transaction. Test that the file is marked as orphan */ @Test public void atomicWriteTest() { HybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); HybridFileBackedSqlStorage<FormInstance> userFixtureStorage = CommCareApplication.instance().getFileBackedUserStorage("fixture", FormInstance.class); FormInstance form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); form.setID(-1); // build a form instance that fails when setID is called, which happens // at the end of db write FormInstanceWithFailures failingForm = new FormInstanceWithFailures(form.getRoot()); FormInstanceWithFailures.setFailOnIdSet(true); File dbDir = userFixtureStorage.getDbDirForTesting(); int fileCountBefore = dbDir.listFiles().length; boolean didWriteFail = false; try { userFixtureStorage.write(failingForm); } catch (RuntimeException e) { didWriteFail = true; } Assert.assertTrue(didWriteFail); // check that the file from the failed write is around int fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountAfter - fileCountBefore == 1); // check that the file is cleared with other orphan files clearOrphanedFiles(); int fileCountAfterClear = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfterClear == 0); } @Test public void moveEncryptedFixtureFromFsToDbAndBack() { HybridFileBackedSqlStorageMock.alwaysPutInDatabase(); HybridFileBackedSqlStorage<FormInstance> userFixtureStorage = CommCareApplication.instance().getFileBackedUserStorage("fixture", FormInstance.class); FormInstance form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); File dbDir = userFixtureStorage.getDbDirForTesting(); int fileCountBefore = dbDir.listFiles().length; // move fixture from filesystem to database String newName = "some_fixture_in_db"; form.setName(newName); userFixtureStorage.update(form.getID(), form); // ensure the data can still be read form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); Assert.assertEquals(newName, form.getName()); // quick test coverage for reading multiple records that have serialized objects stored in db FormInstance form2 = userFixtureStorage.getRecordsForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}).firstElement(); Assert.assertEquals(form2.getName(), form.getName()); // ensure the old file was removed clearOrphanedFiles(); int fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfter == 1); // move fixture back into filesystem HybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); newName = "some_fixture_in_fs"; form.setName(newName); userFixtureStorage.update(form.getID(), form); // ensure the data can still be read form = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); Assert.assertEquals(newName, form.getName()); fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore == fileCountAfter); userFixtureStorage.remove(form.getID()); fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfter == 1); } @Test public void moveUnencryptedFixtureFromFsToDbAndBack() { UnencryptedHybridFileBackedSqlStorageMock.alwaysPutInDatabase(); UnencryptedHybridFileBackedSqlStorage<FormInstance> appFixtureStorage = CommCareApplication.instance().getCurrentApp().getFileBackedStorage("fixture", FormInstance.class); FormInstance form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); File dbDir = appFixtureStorage.getDbDirForTesting(); int fileCountBefore = dbDir.listFiles().length; // move fixture from filesystem to database String newName = "some_fixture_in_db"; form.setName(newName); appFixtureStorage.update(form.getID(), form); // ensure the data can still be read form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); Assert.assertEquals(newName, form.getName()); // ensure the old file was removed clearOrphanedUnencryptedFiles(); int fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfter == 1); // move fixture back into filesystem UnencryptedHybridFileBackedSqlStorageMock.alwaysPutInFilesystem(); newName = "some_fixture_in_fs"; form.setName(newName); appFixtureStorage.update(form.getID(), form); // ensure the data can still be read form = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); Assert.assertEquals(newName, form.getName()); fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore == fileCountAfter); appFixtureStorage.remove(form.getID()); fileCountAfter = dbDir.listFiles().length; Assert.assertTrue(fileCountBefore - fileCountAfter == 1); } @Test public void testDbWriteAndUpdate() { HybridFileBackedSqlStorageMock.alwaysPutInDatabase(); UnencryptedHybridFileBackedSqlStorageMock.alwaysPutInDatabase(); // unencrypted write / update test UnencryptedHybridFileBackedSqlStorage<FormInstance> appFixtureStorage = CommCareApplication.instance().getCurrentApp().getFileBackedStorage("fixture", FormInstance.class); FormInstance appLevelFixture = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"user-groups"}); // test write appLevelFixture.setID(-1); appLevelFixture.initialize(null, "new-user-groups"); appFixtureStorage.write(appLevelFixture); // test read appFixtureStorage.read(appLevelFixture.getID()); // test update String newName = "some_fixture"; appLevelFixture.setName(newName); appFixtureStorage.update(appLevelFixture.getID(), appLevelFixture); // ensure the data can still be read appLevelFixture = appFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"new-user-groups"}); Assert.assertEquals(newName, appLevelFixture.getName()); // encrypted write / update test HybridFileBackedSqlStorage<FormInstance> userFixtureStorage = CommCareApplication.instance().getFileBackedUserStorage("fixture", FormInstance.class); FormInstance userLevelFixture = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"commtrack:programs"}); // test write userLevelFixture.setID(-1); userLevelFixture.initialize(null, "new-commtrack"); userFixtureStorage.write(userLevelFixture); // test read userFixtureStorage.read(userLevelFixture.getID()); // test update userLevelFixture.setName(newName); userFixtureStorage.update(userLevelFixture.getID(), userLevelFixture); // ensure the data can still be read userLevelFixture = userFixtureStorage.getRecordForValues(new String[]{FormInstance.META_ID}, new String[]{"new-commtrack"}); Assert.assertEquals(newName, userLevelFixture.getName()); } private static void clearOrphanedFiles() { HybridFileBackedSqlHelpers.removeOrphanedFiles(CommCareApplication.instance().getUserDbHandle()); } private static void clearOrphanedUnencryptedFiles() { HybridFileBackedSqlHelpers.removeOrphanedFiles(CommCareApp.getAppDatabaseForTesting()); } }