com.cloudant.sync.datastore.DatastoreSchemaTests.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudant.sync.datastore.DatastoreSchemaTests.java

Source

/**
 * Copyright (c) 2014 Cloudant, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 */

package com.cloudant.sync.datastore;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;

import com.cloudant.android.ContentValues;
import com.cloudant.sync.datastore.encryption.NullKeyProvider;
import com.cloudant.sync.datastore.migrations.MigrateDatabase6To100;
import com.cloudant.sync.sqlite.Cursor;
import com.cloudant.sync.sqlite.SQLDatabase;
import com.cloudant.sync.sqlite.SQLDatabaseQueue;
import com.cloudant.sync.sqlite.SQLQueueCallable;
import com.cloudant.sync.util.TestUtils;

import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Tests specific to database migrations and schema.
 */
public class DatastoreSchemaTests {

    @Test
    public void testSchema4UuidsDifferent() {
        // privateUuid and publicUuid have to be unique on the info table for each DB
        String[] schema_Alpha = DatastoreConstants.getSchemaVersion4();
        String[] schema_Beta = DatastoreConstants.getSchemaVersion4();
        assertThat("Schemas should be different", schema_Alpha, not(equalTo(schema_Beta)));
    }

    @Test
    public void testSchemasSame() {
        // for contrast with above test, schema 3 and 5 are identical each time
        String[] schema_Alpha3 = DatastoreConstants.getSchemaVersion3();
        String[] schema_Beta3 = DatastoreConstants.getSchemaVersion3();
        assertThat("Schemas should be the same", schema_Alpha3, equalTo(schema_Beta3));

        String[] schema_Alpha5 = DatastoreConstants.getSchemaVersion5();
        String[] schema_Beta5 = DatastoreConstants.getSchemaVersion5();
        assertThat("Schemas should be the same", schema_Alpha5, equalTo(schema_Beta5));
    }

    @Test
    /**
     * Test the specific migration of a version 6 database to a version 7 database.
     * This migration should add the attachments_key_filename table, and populate
     * it with the existing keys from the attachments table as both key and filename.
     *
     * fixture/datastores-user_version6.zip contains a known database, at version 6 and
     * with two attachments.
     */
    public void migrateVersion6To100()
            throws DatastoreNotCreatedException, ExecutionException, InterruptedException, IOException {
        // Extract database to temp folder
        File temp_folder = new File(TestUtils.createTempTestingDir(this.getClass().getName()));
        File zippedVersion6 = f("fixture/datastores-user_version6.zip");
        Assert.assertTrue(unzipToDirectory(zippedVersion6, temp_folder));

        final String dbPath = j(temp_folder.getAbsolutePath(), "datastores", "testdb", "db.sync");
        SQLDatabaseQueue queue = new SQLDatabaseQueue(dbPath, new NullKeyProvider());

        queue.updateSchema(new MigrateDatabase6To100(), 100);

        // Check migration worked
        queue.submit(new SQLQueueCallable<Object>() {
            @Override
            public Object call(SQLDatabase db) throws Exception {

                Assert.assertEquals("DB version should be 100", 100, db.getVersion());

                Cursor c;

                // Confirm attachments_key_filename exists
                c = db.rawQuery("SELECT count(*) FROM sqlite_master WHERE type=\"table\" AND "
                        + "name=\"attachments_key_filename\";", null);
                c.moveToFirst();
                Assert.assertEquals(1, c.getInt(0));
                c.close();

                // Confirm attachments & attachments_key_filename have the right number of rows
                c = db.rawQuery("SELECT count(*) FROM attachments;", null);
                c.moveToFirst();
                Assert.assertEquals(2, c.getInt(0));
                c.close();
                c = db.rawQuery("SELECT count(*) FROM attachments_key_filename;", null);
                c.moveToFirst();
                Assert.assertEquals(2, c.getInt(0));
                c.close();

                // Confirm keys in attachments are as expected
                c = db.rawQuery(
                        "SELECT key FROM attachments WHERE " + "key=x'68c3b7058dee64eba568746d396e42d7b7a1895b';",
                        null);
                Assert.assertEquals("attachments table has 68c3b70 key", c.getCount(), 1);
                c.close();

                c = db.rawQuery(
                        "SELECT key FROM attachments WHERE " + "key=x'd55f9ac778baf2256fa4de87aac61f590ebe66e0';",
                        null);
                Assert.assertEquals("attachments table has d55f9ac key", c.getCount(), 1);
                c.close();

                // We should have new table attachments_key_filename with two rows,
                // which should be
                // key & filename both equal to:
                // 68c3b7058dee64eba568746d396e42d7b7a1895b
                // d55f9ac778baf2256fa4de87aac61f590ebe66e0
                c = db.rawQuery("SELECT key,filename FROM attachments_key_filename WHERE "
                        + "key=\"68c3b7058dee64eba568746d396e42d7b7a1895b\";", null);
                Assert.assertEquals("attachments_key_filename table has 68c3b70 key", c.getCount(), 1);
                c.moveToFirst();
                Assert.assertEquals("68c3b7058dee64eba568746d396e42d7b7a1895b", c.getString(0));
                Assert.assertEquals("68c3b7058dee64eba568746d396e42d7b7a1895b", c.getString(1));
                c.close();

                c = db.rawQuery("SELECT key,filename FROM attachments_key_filename WHERE "
                        + "key=\"d55f9ac778baf2256fa4de87aac61f590ebe66e0\";", null);
                Assert.assertEquals("attachments_key_filename table has d55f9ac key", c.getCount(), 1);
                c.moveToFirst();
                Assert.assertEquals("d55f9ac778baf2256fa4de87aac61f590ebe66e0", c.getString(0));
                Assert.assertEquals("d55f9ac778baf2256fa4de87aac61f590ebe66e0", c.getString(1));
                c.close();

                // Test we can't insert duplicate key
                ContentValues cv = new ContentValues();
                cv.put("key", "d55f9ac778baf2256fa4de87aac61f590ebe66e0");
                cv.put("filename", "nonExistentValue");
                Assert.assertEquals("Could insert duplicate key", -1, db.insert("attachments_key_filename", cv));

                // Test we can't insert duplicate filename
                cv = new ContentValues();
                cv.put("key", "nonExistentValue");
                cv.put("filename", "d55f9ac778baf2256fa4de87aac61f590ebe66e0");
                Assert.assertEquals("Could insert duplicate key", -1, db.insert("attachments_key_filename", cv));

                return null;
            }
        }).get();

        queue.shutdown();

        TestUtils.deleteTempTestingDir(temp_folder.getAbsolutePath());
    }

    @Test
    /**
     * Ensure database is migrated to version 100 or above when opening.
     *
     * fixture/datastores-user_version6.zip contains a known database, at version 6 and
     * with two attachments.
     */
    public void migrationToAtLeast100OnDatastoreOpen()
            throws DatastoreNotCreatedException, ExecutionException, InterruptedException, IOException {
        // Extract database to temp folder
        File temp_folder = new File(TestUtils.createTempTestingDir(this.getClass().getName()));
        File zippedVersion6 = f("fixture/datastores-user_version6.zip");
        Assert.assertTrue(unzipToDirectory(zippedVersion6, temp_folder));

        // Datastore manager the temp folder
        DatastoreImpl datastore = (DatastoreImpl) new DatastoreManager(
                new File(temp_folder, "datastores").getAbsolutePath()).openDatastore("testdb");

        // Check migration worked
        datastore.runOnDbQueue(new SQLQueueCallable<Object>() {
            @Override
            public Object call(SQLDatabase db) throws Exception {
                Assert.assertTrue("DB version should be 100 or more", db.getVersion() >= 100);
                return null;
            }
        }).get();

        datastore.close();

        TestUtils.deleteTempTestingDir(temp_folder.getAbsolutePath());
    }

    @SuppressWarnings("ResultOfMethodCallIgnored") // mkdirs result should be fine
    private boolean unzipToDirectory(File zipPath, File outputDirectory) {
        try {

            ZipFile zipFile = new ZipFile(zipPath);
            try {
                Enumeration<? extends ZipEntry> entries = zipFile.entries();
                while (entries.hasMoreElements()) {
                    ZipEntry entry = entries.nextElement();
                    File entryDestination = new File(outputDirectory, entry.getName());
                    if (entry.isDirectory())
                        entryDestination.mkdirs();
                    else {
                        entryDestination.getParentFile().mkdirs();
                        InputStream in = zipFile.getInputStream(entry);
                        OutputStream out = new FileOutputStream(entryDestination);
                        IOUtils.copy(in, out);
                        IOUtils.closeQuietly(in);
                        out.close();
                    }
                }
            } finally {
                zipFile.close();
            }

            return true;

        } catch (Exception ex) {
            return false;
        }
    }

    private static String j(String... components) {
        String path = components[0];
        String[] remainder = Arrays.copyOfRange(components, 1, components.length);
        for (String pathComponent : remainder) {
            path = path + File.separator + pathComponent;
        }
        return path;
    }

    private static File f(String filename) {
        return TestUtils.loadFixture(filename);
    }

}