/** * Copyright (c) 2016 Couchbase, Inc. All rights reserved. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.couchbase.lite; import com.couchbase.lite.internal.RevisionInternal; import com.couchbase.lite.mockserver.MockCheckpointPut; import com.couchbase.lite.mockserver.MockDispatcher; import com.couchbase.lite.mockserver.MockHelper; import com.couchbase.lite.replicator.Replication; import com.couchbase.lite.store.SQLiteStore; import com.couchbase.lite.support.FileDirUtils; import com.couchbase.lite.support.Version; import com.couchbase.lite.util.Log; import com.couchbase.lite.util.Utils; import com.couchbase.lite.util.ZipUtils; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockWebServer; public class ManagerTest extends LiteTestCaseWithDB { public static final String TAG = "ManagerTest"; public void testServer() throws CouchbaseLiteException { //to ensure this test is easily repeatable we will explicitly remove //any stale foo.cblite boolean mustExist = true; Database old = manager.getDatabase("foo", mustExist); if (old != null) { old.delete(); } mustExist = false; Database db = manager.getDatabase("foo", mustExist); assertNotNull(db); assertEquals("foo", db.getName()); assertTrue(db.getPath().startsWith( getDefaultTestContext(false).getFilesDir().getAbsolutePath())); assertFalse(db.exists()); // because foo doesn't exist yet List<String> databaseNames = manager.getAllDatabaseNames(); assertTrue(!databaseNames.contains("foo")); db.open(); assertTrue(db.exists()); databaseNames = manager.getAllDatabaseNames(); assertTrue(databaseNames.contains("foo")); db.close(); db.delete(); } public void testReplaceDatabaseNamedNoAttachments() throws CouchbaseLiteException { // public void replaceDatabase(String, InputStream, Map<String, InputStream>) is // for .cblite. This is only applicable for SQLite if (!isSQLiteDB()) return; //Copy database from assets to local storage InputStream dbStream = getAsset("noattachments.cblite"); manager.replaceDatabase("replaced", dbStream, null); //Now validate the number of files in the DB assertEquals(10, manager.getDatabase("replaced").getDocumentCount()); } public void testReplaceDatabaseNamedWithAttachments() throws CouchbaseLiteException { // public void replaceDatabase(String, InputStream, Map<String, InputStream>) is // for .cblite. This is only applicable for SQLite if (!isSQLiteDB()) return; InputStream dbStream = getAsset("withattachments.cblite"); Map<String, InputStream> attachments = new HashMap<String, InputStream>(); InputStream blobStream = getAsset("attachments/356a192b7913b04c54574d18c28d46e6395428ab.blob"); attachments.put("356a192b7913b04c54574d18c28d46e6395428ab.blob", blobStream); manager.replaceDatabase("replaced2", dbStream, attachments); //Validate the number of files in the DB assertEquals(1, manager.getDatabase("replaced2").getDocumentCount()); //get the attachment from the document Document doc = manager.getDatabase("replaced2").getExistingDocument("168e0c56-4588-4df4-8700-4d5115fa9c74"); assertNotNull(doc); RevisionInternal gotRev1 = database.getDocument(doc.getId(), doc.getCurrentRevisionId(), true); } /** * Test for void replaceDatabase(String, InputStream, Map<String, InputStream>) */ public void testReplaceDatabase() throws CouchbaseLiteException, IOException { // public void replaceDatabase(String, InputStream, Map<String, InputStream>) is // for .cblite. This is only applicable for SQLite // Test for pre-built database test from CBL Android 1.0.4 { // NOTE: in assets, "." -> "/" String[] attachments = { "7f0e1bc2d59e1607f21b984ce6fbfe777e6f458e.blob", "fcc1350f03cad8acfc7c13bf8e1cc70485825bda.blob"}; validateReplaceDatabase("replacedb/android104/androiddb.cblite", 2, "replacedb/android104/androiddb/attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentFromAndroid(replacedDatabase); } }); } // Test for pre-built database test from CBL Android 1.1.0 { String[] attachments = { "7F0E1BC2D59E1607F21B984CE6FBFE777E6F458E.blob", "FCC1350F03CAD8ACFC7C13BF8E1CC70485825BDA.blob"}; validateReplaceDatabase("replacedb/android110/androiddb.cblite", 2, "replacedb/android110/androiddb attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentFromAndroid(replacedDatabase); } }); } // Test for pre-built database test from CBL iOS 1.0.4 { String[] attachments = { "3F58B9908FECA2CABBE39FFD04347B9048212A9B.blob", "89379B9D06B399D0214411FAE32F44E89AB04A87.blob"}; validateReplaceDatabase("replacedb/ios104/iosdb.cblite", 1, "replacedb/ios104/iosdb attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentFromIOS(replacedDatabase); } }); } // Test for pre-built database test from CBL iOS 1.1.0 { String[] attachments = { "3F58B9908FECA2CABBE39FFD04347B9048212A9B.blob", "89379B9D06B399D0214411FAE32F44E89AB04A87.blob"}; validateReplaceDatabase("replacedb/ios110/iosdb.cblite", 1, "replacedb/ios110/iosdb attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentFromIOS(replacedDatabase); } }); } // Test for pre-built database test from CBL .NET 1.0.4 { String[] attachments = {"d6ea829121af9d025ec61d8157fcf8ea4b445129.blob"}; validateReplaceDatabase("replacedb/net104/netdb.cblite", 2, "replacedb/net104/netdb/attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentfromNET1(replacedDatabase); } }); } // Test for pre-built database test from CBL .NET 1.1.0 { String[] attachments = {"d6ea829121af9d025ec61d8157fcf8ea4b445129.blob"}; validateReplaceDatabase("replacedb/net110/netdb.cblite", 1, "replacedb/net110/netdb attachments", attachments, new ValidateDatabaseCallback() { @Override public void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException { validateDatabaseContentNET2(replacedDatabase); } }); } } interface ValidateDatabaseCallback { void validate(Database replacedDatabase) throws CouchbaseLiteException, IOException; } protected void validateReplaceDatabase(String databaseFilename, int numDocs, String attachmentDirectory, String[] attachmentNames, ValidateDatabaseCallback callback) throws CouchbaseLiteException, IOException { InputStream dbStream = getAsset(databaseFilename); assertNotNull(dbStream); Map<String, InputStream> attachmentStreams = new HashMap<String, InputStream>(); for (String attachment : attachmentNames) { InputStream blobStream = getAsset(String.format(Locale.ENGLISH, "%s/%s", attachmentDirectory, attachment)); assertNotNull(blobStream); attachmentStreams.put(attachment, blobStream); } manager.replaceDatabase("replaced_database", dbStream, attachmentStreams); Database replacedDatabase = manager.getDatabase("replaced_database"); assertEquals(numDocs, replacedDatabase.getDocumentCount()); if (callback != null) callback.validate(replacedDatabase); int counter = 0; // Create a view and register its map function: View v = replacedDatabase.getView("view_test"); v.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), document); } }, "1"); v.updateIndex(); Query q = v.createQuery(); QueryEnumerator r = q.run(); for (Iterator<QueryRow> it = r; it.hasNext(); ) { QueryRow row = it.next(); Log.w(Log.TAG, row.getDocument().getProperties().toString()); counter++; } assertEquals(numDocs, counter); replacedDatabase.delete(); } protected void validateDatabaseContentFromAndroid(Database replacedDatabase) throws CouchbaseLiteException, IOException { for (int i = 0; i < replacedDatabase.getDocumentCount(); i++) { Document doc = replacedDatabase.getDocument("doc" + String.valueOf(i)); Map<String, Object> props = doc.getProperties(); assertEquals(i, Integer.parseInt((String) props.get("key"))); List<Attachment> attachments = doc.getCurrentRevision().getAttachments(); assertEquals(1, attachments.size()); Attachment attachment = attachments.get(0); assertNotNull(attachment.getContentType()); assertTrue(attachment.getContentType().contains("text/plain")); assertEquals("file_" + String.valueOf(i) + ".txt", attachment.getName()); BufferedReader br = new BufferedReader(new InputStreamReader(attachment.getContent())); String str = br.readLine(); assertEquals("content " + String.valueOf(i), str); br.close(); } } protected void validateDatabaseContentFromIOS(Database replacedDatabase) throws CouchbaseLiteException, IOException { for (int i = 1; i <= replacedDatabase.getDocumentCount(); i++) { Document doc = replacedDatabase.getDocument("doc" + String.valueOf(i)); List<Attachment> attachments = doc.getCurrentRevision().getAttachments(); assertEquals(2, attachments.size()); // attachment order is not guaranteed Attachment attachment0 = attachments.get(0); Attachment attachment1 = attachments.get(1); assertFalse(attachment0.getName().equals(attachment1.getName())); assertTrue("attach1".equals(attachment0.getName()) || "attach2".equals(attachment0.getName())); assertTrue("attach1".equals(attachment1.getName()) || "attach2".equals(attachment1.getName())); assertNotNull(attachment0.getContentType()); assertTrue(attachment0.getContentType().contains("text/plain")); assertNotNull(attachment1.getContentType()); assertTrue(attachment1.getContentType().contains("text/plain")); BufferedReader br0 = new BufferedReader(new InputStreamReader(attachment0.getContent())); String str0 = br0.readLine(); assertTrue(str0.length() > 0); br0.close(); BufferedReader br1 = new BufferedReader(new InputStreamReader(attachment1.getContent())); String str1 = br1.readLine(); assertTrue(str1.length() > 0); br1.close(); } } protected void validateDatabaseContentfromNET1(Database replacedDatabase) throws CouchbaseLiteException, IOException { Document doc = replacedDatabase.getDocument("doc2"); List<Attachment> attachments = doc.getCurrentRevision().getAttachments(); assertEquals(1, attachments.size()); Attachment attachment = attachments.get(0); assertEquals("image", attachment.getName()); assertNotNull(attachment.getContentType()); assertTrue(attachment.getContentType().contains("image/png")); InputStream is = attachment.getContent(); assertNotNull(is); is.close(); } protected void validateDatabaseContentNET2(Database replacedDatabase) throws CouchbaseLiteException, IOException { Document doc = replacedDatabase.getDocument("doc1"); List<Attachment> attachments = doc.getCurrentRevision().getAttachments(); assertEquals(1, attachments.size()); Attachment attachment = attachments.get(0); assertEquals("image", attachment.getName()); InputStream is = attachment.getContent(); assertNotNull(is); is.close(); } public void testGetDatabaseConcurrently() throws Exception { final String DATABASE_NAME = "test"; ExecutorService executorService = Executors.newFixedThreadPool(2); try { List<Callable<Void>> callables = new ArrayList<Callable<Void>>(2); for (int i = 0; i < 2; i++) { callables.add(new Callable<Void>() { @Override public Void call() throws Exception { manager.getDatabase(DATABASE_NAME); return null; } }); } List<Future<Void>> results = executorService.invokeAll(callables); for (Future<Void> future : results) { // Will throw an exception, thus failing the test, if anything went wrong. future.get(); } } finally { // Cleanup Database a = manager.getExistingDatabase(DATABASE_NAME); if (a != null) { a.delete(); } // Note: ExecutorService should be called shutdown() Utils.shutdownAndAwaitTermination(executorService); } } /** * Error after close DB client * https://github.com/couchbase/couchbase-lite-java/issues/52 */ public void testClose() throws Exception { Log.d(Log.TAG, "START testClose()"); MockWebServer server = new MockWebServer(); try { MockDispatcher dispatcher = new MockDispatcher(); dispatcher.setServerType(MockDispatcher.ServerType.SYNC_GW); server.setDispatcher(dispatcher); server.start(); // checkpoint PUT or GET response (sticky) (for both push and pull) MockCheckpointPut mockCheckpointPut = new MockCheckpointPut(); mockCheckpointPut.setSticky(true); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, mockCheckpointPut); // create pull replication & start it Replication pull = database.createPullReplication(server.url("/db").url()); pull.setContinuous(true); final CountDownLatch pullIdleState = new CountDownLatch(1); ReplicationIdleObserver pullIdleObserver = new ReplicationIdleObserver(pullIdleState); pull.addChangeListener(pullIdleObserver); pull.start(); // create push replication & start it Replication push = database.createPullReplication(server.url("/db").url()); push.setContinuous(true); final CountDownLatch pushIdleState = new CountDownLatch(1); ReplicationIdleObserver pushIdleObserver = new ReplicationIdleObserver(pushIdleState); push.addChangeListener(pushIdleObserver); push.start(); // wait till both push and pull replicators become idle. assertTrue(pullIdleState.await(30, TimeUnit.SECONDS)); assertTrue(pushIdleState.await(30, TimeUnit.SECONDS)); pull.removeChangeListener(pullIdleObserver); push.removeChangeListener(pushIdleObserver); final CountDownLatch pullStoppedState = new CountDownLatch(1); ReplicationFinishedObserver pullStoppedObserver = new ReplicationFinishedObserver(pullStoppedState); pull.addChangeListener(pullStoppedObserver); final CountDownLatch pushStoppedState = new CountDownLatch(1); ReplicationFinishedObserver pushStoppedObserver = new ReplicationFinishedObserver(pushStoppedState); push.addChangeListener(pushStoppedObserver); // close Manager, which close database(s) and replicator(s) manager.close(); // manager.close() should wait till replicators are closed. // However, notification from replicator is sent by replicator thread. // So it is not synchronized with main thread. // just give 10 sec. assertTrue(pullStoppedState.await(10, TimeUnit.SECONDS)); assertTrue(pushStoppedState.await(10, TimeUnit.SECONDS)); pull.removeChangeListener(pullStoppedObserver); push.removeChangeListener(pushStoppedObserver); // give 3 sec to clean thread status. try { Thread.sleep(3 * 1000); } catch (Exception e) { } // all threads for Executors should be terminated. Set<Thread> threadSet = Thread.getAllStackTraces().keySet(); for (Thread t : threadSet) { if (t.isAlive()) { assertEquals(-1, t.getName().indexOf("CBLManagerWorkExecutor")); assertEquals(-1, t.getName().indexOf("CBLRequestWorker")); } } } finally { // shutdown mock server server.shutdown(); } Log.d(Log.TAG, "END testClose()"); } // #pragma mark - REPLACE DATABASE: // Tool to generate test db. public void testGenerateTestDB() throws Exception { Database db; if(isSQLiteDB()) db = manager.getDatabase("android-sqlite"); else db = manager.getDatabase("android-forestdb"); for (int i = 1; i <= 2; i++) { Document doc = db.getDocument(String.format(Locale.ENGLISH, "doc%d", i)); Map props = new HashMap(); props.put("key", String.valueOf(i)); doc.putProperties(props); UnsavedRevision rev = doc.createRevision(); InputStream stream = new ByteArrayInputStream(String.format(Locale.ENGLISH, "attach%d", i).getBytes()); rev.setAttachment(String.format(Locale.ENGLISH, "attach%d", i), "plain/text", stream); rev.save(); stream.close(); } Map map = new HashMap(); map.put("key", "local1"); db.putLocalDocument("local1", map); } public void test23_ReplaceOldVersionDatabase() throws Exception { List<String[]> dbInfoList = new ArrayList<String[]>(); // Android 1.2.0 (SQLite) String[] android120sqlite = {"1", "Android 1.2.0 SQLite", "android120sqlite.cblite2", "replacedb/android120sqlite.cblite2.zip"}; dbInfoList.add(android120sqlite); // Android 1.2.0 (ForestDB) String[] android120forest = {"1", "Android 1.2.0 ForestDB", "android120forest.cblite2", "replacedb/android120forest.cblite2.zip"}; dbInfoList.add(android120sqlite); // iOS 1.2.0 (SQLite) String[] ios120sqlite = {"2", "iOS 1.2.0 SQLite", "ios120/iosdb.cblite2", "replacedb/ios120.zip"}; dbInfoList.add(ios120sqlite); // iOS 1.2.0 (ForestDB) String[] ios120forest = {"2", "iOS 1.2.0 ForestDB", "ios120-forestdb/iosdb.cblite2", "replacedb/ios120-forestdb.zip"}; dbInfoList.add(ios120forest); // .NET 1.2.0 (SQLite) String[] net120sqlite = {"3", ".NET 1.2.0 SQLite", "netdb.cblite2", "replacedb/net120-sqlite.zip"}; dbInfoList.add(net120sqlite); // .NET 1.2.0 (ForestDB) String[] net120forest = {"3", ".NET 1.2.0 ForestDB", "netdb.cblite2", "replacedb/net120-forestdb.zip"}; dbInfoList.add(net120forest); // Android 1.3.0 (SQLite) String[] android130sqlite = {"1", "Android 1.3.0 SQLite", "android-sqlite.cblite2", "replacedb/android130-sqlite.cblite2.zip"}; dbInfoList.add(android130sqlite); // Android 1.3.0 (ForestDB) String[] android130forest = {"1", "Android 1.3.0 ForestDB", "android-forestdb.cblite2", "replacedb/android130-forestdb.cblite2.zip"}; dbInfoList.add(android130forest); // iOS 1.3.0 (SQLite) String[] ios130sqlite = {"2", "iOS 1.3.0 SQLite", "ios130/iosdb.cblite2", "replacedb/ios130.zip"}; dbInfoList.add(ios130sqlite); // iOS 1.3.0 (ForestDB) String[] ios130forest = {"2", "iOS 1.3.0 ForestDB", "ios130-forestdb/iosdb.cblite2", "replacedb/ios130-forestdb.zip"}; dbInfoList.add(ios130forest); // .NET 1.3.0 (SQLite) String[] net130sqlite = {"3", ".NET 1.3.0 SQLite", "netdb.cblite2", "replacedb/net130-sqlite.zip"}; dbInfoList.add(net130sqlite); // .NET 1.3.0 (ForestDB) String[] net130forest = {"3", ".NET 1.3.0 ForestDB", "netdb.cblite2", "replacedb/net130-forestdb.zip"}; dbInfoList.add(net130forest); // iOS 1.4.0 (SQLite) String[] ios140sqlite = {"2", "iOS 1.4.0 SQLite", "ios140/iosdb.cblite2", "replacedb/ios140.zip"}; dbInfoList.add(ios140sqlite); // iOS 1.4.0 (ForestDB) String[] ios140forest = {"2", "iOS 1.4.0 ForestDB", "ios140-forestdb/iosdb.cblite2", "replacedb/ios140-forestdb.zip"}; dbInfoList.add(ios140forest); // Android 1.4.0 (SQLite) String[] android140sqlite = {"1", "Android 1.4.0 SQLite", "android140-sqlite.cblite2", "replacedb/android140-sqlite.cblite2.zip"}; dbInfoList.add(android140sqlite); // Android 1.4.0 (ForestDB) String[] android140forest = {"1", "Android 1.4.0 ForestDB", "android140-forestdb.cblite2", "replacedb/android140-forestdb.cblite2.zip"}; dbInfoList.add(android140forest); // .NET 1.4.0 (SQLite) String[] net140sqlite = {"3", ".NET 1.4.0 SQLite", "netdb.cblite2", "replacedb/net140-sqlite.zip"}; dbInfoList.add(net140sqlite); // .NET 1.4.0 (ForestDB) String[] net140forest = {"3", ".NET 1.4.0 ForestDB", "netdb.cblite2", "replacedb/net140-forestdb.zip"}; dbInfoList.add(net140forest); for (final String[] dbInfo : dbInfoList) { Log.i(TAG, "DB Type: " + dbInfo[1]); File srcDir = new File(manager.getContext().getFilesDir(), dbInfo[2]); FileDirUtils.deleteRecursive(srcDir); ZipUtils.unzip(getAsset(dbInfo[3]), manager.getContext().getFilesDir()); testReplaceDatabaseWithCBLite2("replacedb", srcDir.getAbsolutePath(), new ReplaceDatabaseCallback() { @Override public void onComplete(Database db, QueryEnumerator e) throws CouchbaseLiteException, IOException { // Check Stored Documents assertEquals(2, e.getCount()); for (int i = 0; i < 2; i++) { Document doc = e.getRow(i).getDocument(); assertNotNull(doc); assertEquals("doc" + (i + 1), doc.getId()); assertEquals(2, doc.getRevisionHistory().size()); Map<String, Object> props = doc.getProperties(); if (dbInfo[0].equals("1"))//android assertEquals(i + 1, Integer.parseInt((String) props.get("key"))); else if (dbInfo[0].equals("2")) // iOS assertEquals("bar", (String) props.get("foo")); assertEquals(1, doc.getCurrentRevision().getAttachments().size()); Attachment att = doc.getCurrentRevision().getAttachment("attach" + String.valueOf(i + 1)); assertNotNull(att); if (!dbInfo[0].equals("3")) {// .NET BufferedReader br = new BufferedReader(new InputStreamReader(att.getContent())); String str = br.readLine(); assertEquals("attach" + String.valueOf(i + 1), str); br.close(); } } // Check Local Doc Map<String, Object> local = db.getExistingLocalDocument("local1"); assertNotNull(local); if (dbInfo[0].equals("1"))//android assertEquals("local1", local.get("key")); else if (dbInfo[0].equals("2")) // iOS assertEquals("bar", local.get("foo")); assertEquals("1-local", local.get("_rev")); assertEquals("_local/local1", local.get("_id")); } }); } } interface ReplaceDatabaseCallback { void onComplete(Database db, QueryEnumerator e) throws CouchbaseLiteException, IOException; } void testReplaceDatabaseWithCBLite2(String name, String databaseDir, ReplaceDatabaseCallback callback) throws CouchbaseLiteException, IOException { assertTrue(manager.replaceDatabase(name, databaseDir)); checkReplacedDatabase(name, callback); Database replacedb = manager.getDatabase(name); replacedb.delete(); } void checkReplacedDatabase(String name, ReplaceDatabaseCallback callback) throws CouchbaseLiteException, IOException { Database replaceDb = this.manager.getExistingDatabase(name); assertNotNull(replaceDb); View view = replaceDb.getView("myview"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), null); } }, "1.0"); Query query = view.createQuery(); assertNotNull(query); query.setPrefetch(true); QueryEnumerator e = query.run(); assertNotNull(e); if (callback != null) callback.onComplete(replaceDb, e); } public void testUpgradeDatabaseFrom120() throws Exception { _testUpgradeDatabase("ios120"); } public void testUpgradeDatabaseFrom130() throws Exception { _testUpgradeDatabase("ios130"); } public void testUpgradeDatabaseFrom140() throws Exception { _testUpgradeDatabase("ios140"); } private void _testUpgradeDatabase(String dbname) throws Exception { // Install a canned database: File srcDir = new File(manager.getContext().getFilesDir(), dbname+ "/iosdb.cblite2"); FileDirUtils.deleteRecursive(srcDir); ZipUtils.unzip(getAsset("replacedb/"+dbname+".zip"), manager.getContext().getFilesDir()); manager.replaceDatabase("replacedb", srcDir.getAbsolutePath()); // Open installed db with storageType set to this test's storage type: DatabaseOptions options = new DatabaseOptions(); options.setStorageType(isSQLiteDB() ? Manager.SQLITE_STORAGE : Manager.FORESTDB_STORAGE); Database replacedb = manager.openDatabase("replacedb", options); assertNotNull(replacedb); // Verify storage type matchs what we requested: Class forestDBStoreClass = Class.forName("com.couchbase.lite.store.ForestDBStore"); Class storeClass = isSQLiteDB() ? SQLiteStore.class : forestDBStoreClass; assertTrue(replacedb.getStore().getClass().equals(storeClass)); // Test db contents: checkReplacedDatabase("replacedb", new ReplaceDatabaseCallback() { @Override public void onComplete(Database db, QueryEnumerator e) throws CouchbaseLiteException, IOException { // Check Stored Documents assertEquals(2, e.getCount()); for (int i = 0; i < 2; i++) { Document doc = e.getRow(i).getDocument(); assertNotNull(doc); assertEquals("doc" + (i + 1), doc.getId()); assertEquals(2, doc.getRevisionHistory().size()); Map<String, Object> props = doc.getProperties(); assertEquals("bar", (String) props.get("foo")); assertEquals(1, doc.getCurrentRevision().getAttachments().size()); Attachment att = doc.getCurrentRevision().getAttachment("attach" + String.valueOf(i + 1)); assertNotNull(att); BufferedReader br = new BufferedReader(new InputStreamReader(att.getContent())); String str = br.readLine(); assertEquals("attach" + String.valueOf(i + 1), str); br.close(); } // NOTE: Upgrade does not support local doc?? // check local doc Map<String, Object> local = db.getExistingLocalDocument("local1"); assertNotNull(local); assertEquals("bar", local.get("foo")); assertEquals("1-local", local.get("_rev")); assertEquals("_local/local1", local.get("_id")); } }); // Close and re-open the db using SQLite storage type. Should fail if it used to be ForestDB: assertTrue(replacedb.close()); options.setStorageType(Manager.SQLITE_STORAGE); CouchbaseLiteException error = null; try { replacedb = null; replacedb = manager.openDatabase("replacedb", options); } catch (CouchbaseLiteException e) { error = e; } if (isSQLiteDB()) { assertNotNull(replacedb); } else { assertNull("Incorrectly re-opened ForestDB db as SQLite", replacedb); assertNotNull(error); assertEquals(Status.INVALID_STORAGE_TYPE, error.getCBLStatus().getCode()); assertEquals(406, error.getCBLStatus().getHTTPCode()); } } public void testGetUserAgent() { String userAgent = Manager.getUserAgent(); assertTrue(userAgent.indexOf(Manager.PRODUCT_NAME + "/" + Version.SYNC_PROTOCOL_VERSION) != -1); } /** * Test Database upgrade from Schema v1 to v2, Normal scenario */ public void testUpgradeDatabaseFrom110() throws Exception { _testUpgradeDatabaseV1("ios110", "iosdb", new ValidateDatabaseCallback() { @Override public void validate(Database db) throws CouchbaseLiteException, IOException { validateDatabaseContentFromIOS(db); } }); _testUpgradeDatabaseV1("android110", "androiddb", new ValidateDatabaseCallback() { @Override public void validate(Database db) throws CouchbaseLiteException, IOException { validateDatabaseContentFromAndroid(db); } }); } private void _testUpgradeDatabaseV1(String zipFile, String dbname, ValidateDatabaseCallback callback) throws Exception { // close manager if (manager != null) { manager.close(); manager = null; } // clean folder & generate new context Context ctx = getTestContext(zipFile, true); assertNotNull(ctx); File rootDir = getTestContext("").getFilesDir(); // Install a canned database: File srcDir = new File(rootDir, zipFile); FileDirUtils.deleteRecursive(srcDir); ZipUtils.unzip(getAsset("replacedb/" + zipFile + ".zip"), rootDir); // Open new manager -> In Manager constructor, database upgrading is executed Manager mgr = new Manager(ctx, new ManagerOptions()); if (useForestDB) mgr.setStorageType(Manager.FORESTDB_STORAGE); // Open database Database db = mgr.getExistingDatabase(dbname); assertNotNull(db); assertTrue(db.exists()); // validate upgrade db callback.validate(db); // close db & mgr db.close(); mgr.close(); // check if old db is deleted File cbliteFile = new File(ctx.getFilesDir(), dbname+".cblite"); File attachDir = new File(ctx.getFilesDir(), dbname+" attachments"); assertFalse(cbliteFile.exists()); assertFalse(attachDir.exists()); // check if temporary db is deleted File tmpDbDir = new File(ctx.getFilesDir(), dbname+".tmp.cblite2"); assertFalse(tmpDbDir.exists()); } /** * Test Database upgrade from Schema v1 to v2, With Fake temporary database upgrade file */ public void testUpgradeDatabaseFrom110WithFakeTempDb() throws Exception { _testUpgradeDatabaseV1WithFakeTempDb("ios110", "iosdb", new ValidateDatabaseCallback() { @Override public void validate(Database db) throws CouchbaseLiteException, IOException { validateDatabaseContentFromIOS(db); } }); _testUpgradeDatabaseV1WithFakeTempDb("android110", "androiddb", new ValidateDatabaseCallback() { @Override public void validate(Database db) throws CouchbaseLiteException, IOException { validateDatabaseContentFromAndroid(db); } }); } private void _testUpgradeDatabaseV1WithFakeTempDb(String zipFile, String dbname, ValidateDatabaseCallback callback) throws Exception { // Close manager if (manager != null) { manager.close(); manager = null; } // Clean folder & generate new context Context ctx = getTestContext(zipFile, true); assertNotNull(ctx); File rootDir = getTestContext("").getFilesDir(); File srcDir = new File(rootDir, zipFile); FileDirUtils.deleteRecursive(srcDir); // Create temporary database to fake pending upgrade temporary file Manager mgr = new Manager(ctx, new ManagerOptions()); DatabaseOptions opt = new DatabaseOptions(); opt.setCreate(true); Database tmpDB = mgr.openDatabase(dbname + ".tmp", opt); assertNotNull(tmpDB); Document tmpDoc = tmpDB.createDocument(); Map<String, Object> props = new HashMap<String, Object>(); props.put("tmp", "Temporary"); tmpDoc.putProperties(props); // Close the temporary database tmpDB.close(); // Make sure temporary db exists File tmpDbDir = new File(ctx.getFilesDir(), dbname+".tmp.cblite2"); assertTrue(tmpDbDir.isDirectory()); assertTrue(tmpDbDir.exists()); // Install a canned database: ZipUtils.unzip(getAsset("replacedb/" + zipFile + ".zip"), rootDir); // Re-Open new manager -> In Manager constructor, database upgrading is executed mgr = new Manager(ctx, new ManagerOptions()); if (useForestDB) mgr.setStorageType(Manager.FORESTDB_STORAGE); // Open database Database db = mgr.getExistingDatabase(dbname); assertNotNull(db); assertTrue(db.exists()); // validate upgrade db callback.validate(db); // close db & mgr db.close(); mgr.close(); // check if old db is deleted File cbliteFile = new File(ctx.getFilesDir(), dbname+".cblite"); File attachDir = new File(ctx.getFilesDir(), dbname+" attachments"); assertFalse(cbliteFile.exists()); assertFalse(attachDir.exists()); // check if temporary db is deleted assertFalse(tmpDbDir.exists()); } public void _testPrepareDB() throws CouchbaseLiteException { Database db; DatabaseOptions opt = new DatabaseOptions(); opt.setCreate(true); if (!isUseForestDB()) { db = manager.openDatabase("android-sqlite", opt); } else { opt.setStorageType(Manager.FORESTDB_STORAGE); db = manager.openDatabase("android-forestdb", opt); } try { // local doc Map<String, Object> localProps = new HashMap<String, Object>(); localProps.put("key", "local1"); db.putLocalDocument("local1", localProps); // documents for (int i = 1; i <= 2; i++) { Document doc = db.getDocument("doc" + i); Map<String, Object> prop = new HashMap<String, Object>(); prop.put("key", String.valueOf(i)); doc.putProperties(prop); UnsavedRevision rev = doc.createRevision(); byte[] attach = String.format(Locale.ENGLISH, "attach%d", i).getBytes(); InputStream in = new ByteArrayInputStream(attach); rev.setAttachment("attach" + i, "text/plain", in); rev.save(); } } finally { db.close(); } } }