// // Copyright (c) 2016 Couchbase, 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.couchbase.lite; import com.couchbase.lite.internal.Body; import com.couchbase.lite.internal.RevisionInternal; import com.couchbase.lite.mockserver.MockDispatcher; import com.couchbase.lite.mockserver.MockDocumentGet; import com.couchbase.lite.mockserver.MockHelper; import com.couchbase.lite.replicator.Replication; import com.couchbase.lite.replicator.ReplicationState; import com.couchbase.lite.router.Router; import com.couchbase.lite.router.RouterCallbackBlock; import com.couchbase.lite.router.URLConnection; import com.couchbase.lite.router.URLStreamHandlerFactory; import com.couchbase.lite.storage.SQLiteNativeLibrary; import com.couchbase.lite.store.SQLiteStore; import com.couchbase.lite.support.FileDirUtils; import com.couchbase.lite.support.Version; import com.couchbase.lite.support.security.SymmetricKey; import com.couchbase.lite.util.Log; import com.couchbase.lite.util.Utils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import junit.framework.Assert; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.security.NoSuchAlgorithmException; 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.Properties; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.crypto.Cipher; import okhttp3.mockwebserver.RecordedRequest; import okio.Buffer; public class LiteTestCaseWithDB extends LiteTestCase { public static final String TAG = "LiteTestCaseWithDB"; private static boolean initializedUrlHandler = false; protected ObjectMapper mapper = new ObjectMapper(); protected Manager manager = null; protected Database database = null; protected static final String DEFAULT_TEST_DB = "cblite-test"; protected static final String DEFAULT_TEST_DIR_NAME = "test"; protected boolean useForestDB = false; private boolean encryptedAttachmentStore = false; protected boolean isSQLiteDB() { return SQLiteStore.class.equals(database.getStore().getClass()); } protected boolean isUseForestDB() { return useForestDB; } @Override public void runBare() throws Throwable { long start = System.currentTimeMillis(); loadCustomProperties(); // Run Unit Test with SQLiteStore if (getTestStorageType() != 2) { useForestDB = false; super.runBare(); } // Run Unit Test with ForestDBStore if (getTestStorageType() != 1) { useForestDB = true; super.runBare(); } long end = System.currentTimeMillis(); String name = getName(); long duration = (end - start) / 1000; Log.e(TAG, "DURATION: %s: %d sec%s", name, duration, duration >= 6 ? " - [SLOW]" : ""); } public void runBare(boolean forestDB) throws Throwable { long start = System.currentTimeMillis(); useForestDB = forestDB; super.runBare(); long end = System.currentTimeMillis(); String name = getName(); long duration = (end - start) / 1000; Log.e(TAG, "DURATION: %s: %d sec%s", name, duration, duration >= 6 ? " - [SLOW]" : ""); } @Override protected void setUp() throws Exception { Log.v(TAG, "setUp"); super.setUp(); loadCustomProperties(); if (!useForestDB) setupSQLiteNativeLibrary(); //for some reason a traditional static initializer causes junit to die if (!initializedUrlHandler) { URLStreamHandlerFactory.registerSelfIgnoreError(); initializedUrlHandler = true; } startCBLite(); startDatabase(); } protected void setupSQLiteNativeLibrary() { int library = getSQLiteLibrary(); if (library == 1) SQLiteNativeLibrary.TEST_NATIVE_LIBRARY_NAME = SQLiteNativeLibrary.JNI_SQLITE_CUSTOM_LIBRARY; else if (library == 2) SQLiteNativeLibrary.TEST_NATIVE_LIBRARY_NAME = SQLiteNativeLibrary.JNI_SQLCIPHER_LIBRARY; else throw new IllegalArgumentException("Invalid Native Library : " + library); } protected static boolean syncgatewayTestsEnabled() { return Boolean.parseBoolean(System.getProperty("syncgatewayTestsEnabled")); } protected static boolean couchdbTestsEnabled() { return Boolean.parseBoolean(System.getProperty("couchdbTestsEnabled")); } protected static boolean multithreadsTestsEnabled() { return Boolean.parseBoolean(System.getProperty("multithreadsTestsEnabled")); } protected static int getSQLiteLibrary() { return Integer.parseInt(System.getProperty("sqliteLibrary")); } protected static int getTestStorageType() { return Integer.parseInt(System.getProperty("storageType")); } protected boolean isEncryptionTestEnabled() { if (!isAndriod()) { boolean support256Key = false; try { support256Key = Cipher.getMaxAllowedKeyLength("AES") >= 256; } catch (NoSuchAlgorithmException e) { } if (!support256Key) return false; } return !isSQLiteDB() || (isSQLiteDB() && SQLiteNativeLibrary.TEST_NATIVE_LIBRARY_NAME == SQLiteNativeLibrary.JNI_SQLCIPHER_LIBRARY); } protected InputStream getAsset(String name) { return this.getClass().getResourceAsStream("/assets/" + name); } protected Context getDefaultTestContext(boolean deleteContent) { return getTestContext(DEFAULT_TEST_DIR_NAME, deleteContent); } protected Context getTestContext(String dirName, boolean deleteContent) { Context context = getTestContext(dirName); if (deleteContent && context.getFilesDir().exists()) { // NOTE: retry and sleep is only for Windows. Other platforms never fail. int counter = 0; while (!FileDirUtils.cleanDirectory(context.getFilesDir()) && counter < 10) { // NOTE: forestdb releses resources in Object.finalize() System.gc(); try { Thread.sleep(1000); } catch (Exception e) { } counter++; } assertTrue(counter < 10); } return context; } protected void startCBLite() throws IOException { Manager.enableLogging(TAG, Log.VERBOSE); Manager.enableLogging(Log.TAG, Log.VERBOSE); Manager.enableLogging(Log.TAG_SYNC, Log.VERBOSE); Manager.enableLogging(Log.TAG_SYNC_ASYNC_TASK, Log.VERBOSE); Manager.enableLogging(Log.TAG_BATCHER, Log.VERBOSE); Manager.enableLogging(Log.TAG_QUERY, Log.VERBOSE); Manager.enableLogging(Log.TAG_VIEW, Log.VERBOSE); Manager.enableLogging(Log.TAG_CHANGE_TRACKER, Log.VERBOSE); Manager.enableLogging(Log.TAG_BLOB_STORE, Log.VERBOSE); Manager.enableLogging(Log.TAG_DATABASE, Log.VERBOSE); Manager.enableLogging(Log.TAG_LISTENER, Log.VERBOSE); Manager.enableLogging(Log.TAG_MULTI_STREAM_WRITER, Log.VERBOSE); Manager.enableLogging(Log.TAG_REMOTE_REQUEST, Log.VERBOSE); Manager.enableLogging(Log.TAG_ROUTER, Log.VERBOSE); Context context = getDefaultTestContext(true); manager = createManager(context); } protected Manager createManager(Context context) throws IOException { ManagerOptions options = new ManagerOptions(); Manager manager = new Manager(context, options); if (useForestDB) manager.setStorageType(Manager.FORESTDB_STORAGE); return manager; } protected void stopCBLite() { int DEFAULT_VALUE = Utils.DEFAULT_TIME_TO_WAIT_4_SHUTDOWN; Utils.DEFAULT_TIME_TO_WAIT_4_SHUTDOWN = 0; try { if (manager != null) { manager.close(); } } finally { Utils.DEFAULT_TIME_TO_WAIT_4_SHUTDOWN = DEFAULT_VALUE; } } protected Database startDatabase() throws CouchbaseLiteException { database = ensureEmptyDatabase(DEFAULT_TEST_DB); return database; } protected void stopDatabase() { if (database != null) { if(!database.close()){ Log.e(TAG, "Error in database.close()"); } } } protected Database ensureEmptyDatabase(String dbName) throws CouchbaseLiteException { Database db = manager.getExistingDatabase(dbName); if (db != null) { db.delete(); } db = manager.getDatabase(dbName); return db; } protected void loadCustomProperties() throws IOException { Properties systemProperties = System.getProperties(); InputStream mainProperties = getAsset("test.properties"); if (mainProperties != null) { systemProperties.load(new InputStreamReader(mainProperties, "UTF-8")); mainProperties.close(); } try { InputStream localProperties = getAsset("local-test.properties"); if (localProperties != null) { systemProperties.load(new InputStreamReader(localProperties, "UTF-8")); localProperties.close(); } } catch (IOException e) { Log.w(TAG, "Error trying to read from local-test.properties, does this file exist?"); } } protected URL getReplicationURL() throws MalformedURLException { return new URL(System.getProperty("replicationUrl")); } protected URL getSyncGatewayURL() throws MalformedURLException { return new URL(System.getProperty("syncgatewayUrl")); } protected URL getCouchDBURL() throws MalformedURLException { return new URL(System.getProperty("couchdbUrl")); } protected String getSyncGatewayHost() throws MalformedURLException { return System.getProperty("syncgatewayHost"); } protected URL getRemoteTestDBURL(String dbName) throws MalformedURLException { URL url = getSyncGatewayURL(); return new URL(url.getProtocol(), url.getHost(), url.getPort(), dbName); } protected URL getAdminReplicationURL() throws MalformedURLException { return new URL(System.getProperty("adminReplicationUrl")); } protected boolean isTestingAgainstSyncGateway() { try { URL url = getReplicationURL(); return url.getPort() == 4984; } catch (MalformedURLException e) { return false; } } @Override protected void tearDown() throws Exception { Log.v(TAG, "tearDown"); super.tearDown(); stopDatabase(); stopCBLite(); } protected Map<String, Object> userProperties(Map<String, Object> properties) { Map<String, Object> result = new HashMap<String, Object>(); for (String key : properties.keySet()) { if (!key.startsWith("_")) { result.put(key, properties.get(key)); } } return result; } public boolean isEncryptedAttachmentStore() { return encryptedAttachmentStore; } public void setEncryptedAttachmentStore(boolean encrypted) throws Exception { SymmetricKey key = encrypted ? new SymmetricKey() : null; database.getAttachmentStore().changeEncryptionKey(key); encryptedAttachmentStore = encrypted; } public Map<String, Object> getReplicationAuthParsedJson() throws IOException { String authJson = "{\n" + " \"facebook\" : {\n" + " \"email\" : \"jchris@couchbase.com\"\n" + " }\n" + " }\n"; ObjectMapper mapper = new ObjectMapper(); Map<String, Object> authProperties = mapper.readValue(authJson, new TypeReference<HashMap<String, Object>>() { }); return authProperties; } public Map<String, Object> getPushReplicationProperties(URL url) throws IOException { Map<String, Object> targetProperties = new HashMap<String, Object>(); targetProperties.put("url", url.toExternalForm()); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("source", DEFAULT_TEST_DB); properties.put("target", targetProperties); return properties; } public Map<String, Object> getPullReplicationProperties(URL url) throws IOException { Map<String, Object> sourceProperties = new HashMap<String, Object>(); sourceProperties.put("url", url.toExternalForm()); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("source", sourceProperties); properties.put("target", DEFAULT_TEST_DB); return properties; } protected URLConnection sendRequest(String method, String path, Map<String, String> headers, Object bodyObj) { try { URL url = new URL("cblite://" + path); URLConnection conn = (URLConnection) url.openConnection(); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); conn.setRequestMethod(method); if (headers != null) { for (String header : headers.keySet()) { conn.setRequestProperty(header, headers.get(header)); } } if (bodyObj != null) { conn.setDoInput(true); ByteArrayInputStream bais = new ByteArrayInputStream(mapper.writeValueAsBytes(bodyObj)); conn.setRequestInputStream(bais); } Router router = new com.couchbase.lite.router.Router(manager, conn); final CountDownLatch latch = new CountDownLatch(1); router.setCallbackBlock(new RouterCallbackBlock() { @Override public void onResponseReady() { latch.countDown(); } }); router.start(); boolean success = false; try { // NOTE: latch.await() should be fine. 60 sec for just in case. success = latch.await(60, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } assertTrue(success); return conn; } catch (MalformedURLException e) { fail(); } catch (IOException e) { fail(); } return null; } protected Object parseJSONResponse(URLConnection conn) { Object result = null; try { byte[] bytes = IOUtils.toByteArray(conn.getResponseInputStream()); result = Manager.getObjectMapper().readValue(bytes, Object.class); } catch (IOException e) { Log.e(TAG, "Cannot get data from connection", e); fail(); } return result; } protected Object sendBody(String method, String path, Object bodyObj, int expectedStatus, Object expectedResult) { return sendBody(method, path, bodyObj, null, expectedStatus, expectedResult); } protected Object sendBody(String method, String path, Object bodyObj, Map<String, String> headers, int expectedStatus, Object expectedResult) { URLConnection conn = sendRequest(method, path, headers, bodyObj); conn.setRequestProperty("Content-Type", "application/json"); Object result = parseJSONResponse(conn); Log.v(TAG, "%s %s --> %d", method, path, conn.getResponseCode()); Assert.assertEquals(expectedStatus, conn.getResponseCode()); if (expectedResult != null) Assert.assertEquals(expectedResult, result); return result; } protected Object send(String method, String path, int expectedStatus, Object expectedResult) { return send(method, path, null, expectedStatus, expectedResult); } protected Object send(String method, String path, Map<String, String> headers, int expectedStatus, Object expectedResult) { return sendBody(method, path, null, headers, expectedStatus, expectedResult); } public static void createDocuments(final Database db, final int n) { db.runInTransaction(new TransactionalTask() { @Override public boolean run() { createDocuments(db, n, false); return true; } }); } public static void createDocuments(final Database db, final int n, boolean transaction) { if (transaction) { createDocuments(db, n); } else { for (int i = 0; i < n; i++) { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testDatabase"); properties.put("sequence", i); createDocumentWithProperties(db, properties); } } } static Future createDocumentsAsync(final Database db, final int n) { return db.runAsync(new AsyncTask() { @Override public void run(Database database) { db.runInTransaction(new TransactionalTask() { @Override public boolean run() { createDocuments(db, n); return true; } }); } }); } public static Document createDocumentWithProperties(Database db, Map<String, Object> properties) { Document doc = db.createDocument(); Assert.assertNotNull(doc); Assert.assertNull(doc.getCurrentRevisionId()); Assert.assertNull(doc.getCurrentRevision()); Assert.assertNotNull("Document has no ID", doc.getId()); // 'untitled' docs are no longer untitled (8/10/12) try { doc.putProperties(properties); } catch (Exception e) { Log.e(TAG, "Error creating document", e); assertTrue("can't create new document in db:" + db.getName() + " with properties:" + properties.toString(), false); } Assert.assertNotNull(doc.getId()); Assert.assertNotNull(doc.getCurrentRevisionId()); Assert.assertNotNull(doc.getUserProperties()); // should be same doc instance, since there should only ever be a single Document // instance for a given document Assert.assertEquals(db.getDocument(doc.getId()), doc); Assert.assertEquals(db.getDocument(doc.getId()).getId(), doc.getId()); return doc; } public static Document createDocWithAttachment(Database database, String attachmentName, String content, Map<String, Object> properties) throws Exception { Document doc = createDocumentWithProperties(database, properties); SavedRevision rev = doc.getCurrentRevision(); assertEquals(rev.getAttachments().size(), 0); assertEquals(rev.getAttachmentNames().size(), 0); assertNull(rev.getAttachment(attachmentName)); ByteArrayInputStream body = new ByteArrayInputStream(content.getBytes()); UnsavedRevision rev2 = doc.createRevision(); rev2.setAttachment(attachmentName, "text/plain; charset=utf-8", body); SavedRevision rev3 = rev2.save(); assertNotNull(rev3); assertEquals(rev3.getAttachments().size(), 1); assertEquals(rev3.getAttachmentNames().size(), 1); Attachment attach = rev3.getAttachment(attachmentName); assertNotNull(attach); assertEquals(doc, attach.getDocument()); assertEquals(attachmentName, attach.getName()); List<String> attNames = new ArrayList<String>(); attNames.add(attachmentName); assertEquals(rev3.getAttachmentNames(), attNames); assertEquals("text/plain; charset=utf-8", attach.getContentType()); InputStream attachInputStream = attach.getContent(); assertEquals(IOUtils.toString(attachInputStream, "UTF-8"), content); attachInputStream.close(); assertEquals(content.getBytes().length, attach.getLength()); return doc; } public static Document createDocWithAttachment(Database database, String attachmentName, String content) throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("foo", "bar"); return createDocWithAttachment(database, attachmentName, content, properties); } public void stopReplication(Replication replication) throws Exception { final CountDownLatch replicationDoneSignal = new CountDownLatch(1); replication.addChangeListener(new ReplicationFinishedObserver(replicationDoneSignal)); replication.stop(); boolean success = replicationDoneSignal.await(30, TimeUnit.SECONDS); assertTrue(success); } protected String createDocumentsForPushReplication(String docIdTimestamp) throws CouchbaseLiteException { return createDocumentsForPushReplication(docIdTimestamp, "png"); } protected Document createDocumentForPushReplication(String docId, String attachmentFileName, String attachmentContentType) throws CouchbaseLiteException { Map<String, Object> docJsonMap = MockHelper.generateRandomJsonMap(); Map<String, Object> docProperties = new HashMap<String, Object>(); docProperties.put("_id", docId); docProperties.putAll(docJsonMap); Document document = database.getDocument(docId); UnsavedRevision revision = document.createRevision(); revision.setProperties(docProperties); if (attachmentFileName != null) { revision.setAttachment( attachmentFileName, attachmentContentType, getAsset(attachmentFileName) ); } revision.save(); return document; } protected String createDocumentsForPushReplication(String docIdTimestamp, String attachmentType) throws CouchbaseLiteException { String doc1Id; String doc2Id;// Create some documents: Map<String, Object> doc1Properties = new HashMap<String, Object>(); doc1Id = String.format(Locale.ENGLISH, "doc1-%s", docIdTimestamp); doc1Properties.put("_id", doc1Id); doc1Properties.put("foo", 1); doc1Properties.put("bar", false); Body body = new Body(doc1Properties); RevisionInternal rev1 = new RevisionInternal(body); Status status = new Status(); rev1 = database.putRevision(rev1, null, false, status); assertEquals(Status.CREATED, status.getCode()); doc1Properties.put("_rev", rev1.getRevID()); doc1Properties.put("UPDATED", true); @SuppressWarnings("unused") RevisionInternal rev2 = database.putRevision(new RevisionInternal(doc1Properties), rev1.getRevID(), false, status); assertEquals(Status.CREATED, status.getCode()); Map<String, Object> doc2Properties = new HashMap<String, Object>(); doc2Id = String.format(Locale.ENGLISH, "doc2-%s", docIdTimestamp); doc2Properties.put("_id", doc2Id); doc2Properties.put("baz", 666); doc2Properties.put("fnord", true); database.putRevision(new RevisionInternal(doc2Properties), null, false, status); assertEquals(Status.CREATED, status.getCode()); Document doc2 = database.getDocument(doc2Id); UnsavedRevision doc2UnsavedRev = doc2.createRevision(); if (attachmentType.equals("png")) { InputStream attachmentStream = getAsset("attachment.png"); doc2UnsavedRev.setAttachment("attachment.png", "image/png", attachmentStream); } else if (attachmentType.equals("txt")) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < 1000; i++) { sb.append("This is a large attachemnt."); } ByteArrayInputStream attachmentStream = new ByteArrayInputStream(sb.toString().getBytes()); doc2UnsavedRev.setAttachment("attachment.txt", "text/plain", attachmentStream); } else { throw new RuntimeException("invalid attachment type: " + attachmentType); } SavedRevision doc2Rev = doc2UnsavedRev.save(); assertNotNull(doc2Rev); return doc1Id; } // timeout - second public void runReplication(Replication replication) throws Exception { runReplication(replication, 120); } // timeout - second public void runReplication(Replication replication, long timeout) throws Exception { final CountDownLatch replicationDoneSignal = new CountDownLatch(1); replication.addChangeListener(new ReplicationFinishedObserver(replicationDoneSignal)); replication.start(); boolean success = replicationDoneSignal.await(timeout, TimeUnit.SECONDS); assertTrue(success); } public void waitForPutCheckpointRequestWithSeq(MockDispatcher dispatcher, int seq) throws TimeoutException { while (true) { RecordedRequest request = dispatcher.takeRequestBlocking(MockHelper.PATH_REGEX_CHECKPOINT); checkUserAgent(request); if (request.getMethod().equals("PUT")) { Buffer clone = request.getBody().clone(); String body = clone.readUtf8(); if (body.indexOf(Integer.toString(seq)) != -1) { // block until response returned dispatcher.takeRecordedResponseBlocking(request); return; } } } } protected void checkUserAgent(RecordedRequest request) { assertNotNull(request); String userAgent = request.getHeader("User-Agent"); assertNotNull(userAgent); Log.v(TAG, "[checkUserAgent(RecordedRequest()] UserAgent: " + userAgent); assertTrue(userAgent.indexOf(Manager.PRODUCT_NAME + "/" + Version.SYNC_PROTOCOL_VERSION) != -1); } protected List<RecordedRequest> waitForPutCheckpointRequestWithSequence(MockDispatcher dispatcher, int expectedLastSequence) throws IOException, TimeoutException { Log.d(TAG, "Wait for PUT checkpoint request with lastSequence: %s", expectedLastSequence); List<RecordedRequest> recordedRequests = new ArrayList<RecordedRequest>(); // wait until mock server gets a checkpoint PUT request with expected lastSequence boolean foundExpectedLastSeq = false; String expectedLastSequenceStr = String.format(Locale.ENGLISH, "%s", expectedLastSequence); while (!foundExpectedLastSeq) { RecordedRequest request = dispatcher.takeRequestBlocking(MockHelper.PATH_REGEX_CHECKPOINT); if (request.getMethod().equals("PUT")) { recordedRequests.add(request); Buffer clone = request.getBody().clone(); Map<String, Object> jsonMap = Manager.getObjectMapper().readValue(clone.readByteArray(), Map.class); Log.i(TAG, "lastSequence=" + jsonMap.get("lastSequence")); Log.i(TAG, "checkpoint request=" + jsonMap); if (jsonMap.containsKey("lastSequence") && ((String) jsonMap.get("lastSequence")).equals(expectedLastSequenceStr)) { foundExpectedLastSeq = true; } // wait until mock server responds to the checkpoint PUT request. // not sure if this is strictly necessary, but might prevent race conditions. dispatcher.takeRecordedResponseBlocking(request); } } return recordedRequests; } protected void validateCheckpointRequestsRevisions(List<RecordedRequest> checkpointRequests) { try { int i = 0; for (RecordedRequest request : checkpointRequests) { Buffer clone = request.getBody().clone(); Map<String, Object> jsonMap = Manager.getObjectMapper().readValue(clone.readByteArray(), Map.class); if (i == 0) { // the first request is not expected to have a _rev field assertFalse(jsonMap.containsKey("_rev")); } else { assertTrue(jsonMap.containsKey("_rev")); // TODO: make sure that each _rev is in sequential order, eg: "0-1", "0-2", etc.. } i += 1; } } catch (IOException e) { throw new RuntimeException(e); } } protected Document createDocWithProperties(Map<String, Object> props) throws CouchbaseLiteException { return createDocWithProperties(props, database); } protected Document createDocWithProperties(Map<String, Object> props, Database db) throws CouchbaseLiteException { Document doc = db.createDocument(); UnsavedRevision revUnsaved = doc.createRevision(); revUnsaved.setUserProperties(props); SavedRevision rev = revUnsaved.save(); assertNotNull(rev); return doc; } protected void attachmentAsserts(String docAttachName, Document doc) throws IOException, CouchbaseLiteException { Attachment attachment = doc.getCurrentRevision().getAttachment(docAttachName); assertNotNull(attachment); byte[] testAttachBytes = MockDocumentGet.getAssetByteArray(docAttachName); int attachLength = testAttachBytes.length; assertEquals(attachLength, attachment.getLength()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream is = attachment.getContent(); baos.write(is); is.close(); byte[] actualAttachBytes = baos.toByteArray(); assertEquals(testAttachBytes.length, actualAttachBytes.length); for (int i = 0; i < actualAttachBytes.length; i++) { boolean ithByteEqual = actualAttachBytes[i] == testAttachBytes[i]; if (!ithByteEqual) { Log.d(Log.TAG, "mismatch"); } assertTrue(ithByteEqual); } } public static SavedRevision createRevisionWithRandomProps(SavedRevision createRevFrom, boolean allowConflict) throws CouchbaseLiteException { Map<String, Object> properties = new HashMap<String, Object>(); properties.put(UUID.randomUUID().toString(), "val"); UnsavedRevision unsavedRevision = createRevFrom.createRevision(); unsavedRevision.setUserProperties(properties); return unsavedRevision.save(allowConflict); } /* Assert that the bulk docs json in request contains given doc. Example bulk docs json. { "docs":[ { "_id":"b7f5664c-7f84-4ddf-9abc-de4e3f376ae4", .. } ] } */ protected void assertBulkDocJsonContainsDoc(RecordedRequest request, Document doc) throws Exception { Buffer clone = request.getBody().clone(); Map<String, Object> bulkDocsJson = Manager.getObjectMapper().readValue(clone.readByteArray(), Map.class); List docs = (List) bulkDocsJson.get("docs"); Map<String, Object> firstDoc = (Map<String, Object>) docs.get(0); assertEquals(doc.getId(), firstDoc.get("_id")); } protected boolean isBulkDocJsonContainsDoc(RecordedRequest request, Document doc) throws Exception { Buffer clone = request.getBody().clone(); Map<String, Object> bulkDocsJson = Manager.getObjectMapper().readValue(clone.readByteArray(), Map.class); List docs = (List) bulkDocsJson.get("docs"); Iterator<Object> itr = docs.iterator(); while (itr.hasNext()) { Map<String, Object> tmp = (Map<String, Object>) itr.next(); if (tmp.get("_id").equals(doc.getId())) return true; } return false; } public static class ReplicationIdleObserver implements Replication.ChangeListener { private CountDownLatch idleSignal; public ReplicationIdleObserver(CountDownLatch idleSignal) { this.idleSignal = idleSignal; } @Override public void changed(Replication.ChangeEvent event) { if (event.getTransition() != null && event.getTransition().getDestination() == ReplicationState.IDLE) { idleSignal.countDown(); } } } public static class ReplicationFinishedObserver implements Replication.ChangeListener { private CountDownLatch doneSignal; public ReplicationFinishedObserver(CountDownLatch doneSignal) { this.doneSignal = doneSignal; } @Override public void changed(Replication.ChangeEvent event) { if (event.getTransition() != null && event.getTransition().getDestination() == ReplicationState.STOPPED) { doneSignal.countDown(); assertEquals(event.getChangeCount(), event.getCompletedChangeCount()); } } } public static class ReplicationOfflineObserver implements Replication.ChangeListener { private CountDownLatch offlineSignal; public ReplicationOfflineObserver(CountDownLatch offlineSignal) { this.offlineSignal = offlineSignal; } @Override public void changed(Replication.ChangeEvent event) { if (event.getTransition() != null && event.getTransition().getDestination() == ReplicationState.OFFLINE) { offlineSignal.countDown(); } } } public static class ReplicationRunningObserver implements Replication.ChangeListener { private CountDownLatch runningSignal; public ReplicationRunningObserver(CountDownLatch runningSignal) { this.runningSignal = runningSignal; } @Override public void changed(Replication.ChangeEvent event) { if (event.getTransition() != null && event.getTransition().getDestination() == ReplicationState.RUNNING) { runningSignal.countDown(); } } } protected void reopenTestDB() throws CouchbaseLiteException { Log.i(TAG, "---- closing db ----"); String dbName = database.getName(); assertTrue(database.close()); Log.i(TAG, "---- reopening db ----"); Database db2 = manager.getDatabase(dbName); assertNotNull(db2); assertTrue(db2 != database); database = db2; } }