/** * 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.store.SQLiteStore; import com.couchbase.lite.util.Log; import com.couchbase.lite.util.TextUtils; import junit.framework.Assert; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Created by andrey on 12/3/13. */ public class ApiTest extends LiteTestCaseWithDB { private int changeCount = 0; //SERVER & DOCUMENTS // - (void) test00_Manager in Database_Tests.m public void testAPIManager() throws Exception { Manager manager = this.manager; Assert.assertTrue(manager != null); for (String dbName : manager.getAllDatabaseNames()) { Database db = manager.getDatabase(dbName); Log.i(TAG, "Database '" + dbName + "':" + db.getDocumentCount() + " documents"); } ManagerOptions options = new ManagerOptions(); options.setReadOnly(true); // We want to create two different managers reading from the same directory so we // create LiteTestContext(false) to make sure we don't delete the directory Manager roManager = new Manager(getDefaultTestContext(false), options); Assert.assertTrue(roManager != null); Database db = roManager.getDatabase("foo"); Assert.assertNull(db); List<String> dbNames = manager.getAllDatabaseNames(); Assert.assertFalse(dbNames.contains("foo")); Assert.assertTrue(dbNames.contains(DEFAULT_TEST_DB)); } // - (void) test02_DeleteDatabase in Database_Tests.m public void testDeleteDatabase() throws Exception { Database deleteme = manager.getDatabase("deleteme"); assertTrue(deleteme.exists()); assertTrue(new File(deleteme.getPath()).exists()); assertTrue(new File(deleteme.getAttachmentStorePath()).exists()); if(isSQLiteDB()) assertTrue(new File(deleteme.getPath(), SQLiteStore.kDBFilename).exists()); else assertTrue(new File(deleteme.getPath(), "db.forest.0").exists()); // hard-coded instead of ForestDBStore.kDBFilename deleteme.delete(); assertFalse(deleteme.exists()); assertFalse(new File(deleteme.getPath()).exists()); assertFalse(new File(deleteme.getAttachmentStorePath()).exists()); assertFalse(new File(deleteme.getPath(), SQLiteStore.kDBFilename).exists()); deleteme.delete(); // delete again, even though already deleted Database deletemeFetched = manager.getExistingDatabase("deleteme"); assertNull(deletemeFetched); } // - (void) test03_CreateDocument in Database_Tests.m public void testCreateDocument() throws CouchbaseLiteException { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testCreateDocument"); properties.put("tag", 1337); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); String docID = doc.getId(); assertTrue("Invalid doc ID: " + docID, docID.length() > 10); String currentRevisionID = doc.getCurrentRevisionId(); assertTrue("Invalid doc revision: " + docID, currentRevisionID.length() > 10); assertEquals(doc.getUserProperties(), properties); assertEquals(db.getDocument(docID), doc); db.clearDocumentCache();// so we can load fresh copies Document doc2 = db.getExistingDocument(docID); assertEquals(doc2.getId(), docID); assertEquals(doc2.getCurrentRevisionId(), currentRevisionID); assertNull(db.getExistingDocument("b0gus")); } public void testDatabaseCompaction() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testDatabaseCompaction"); properties.put("tag", 1337); Document doc = createDocumentWithProperties(database, properties); SavedRevision rev1 = doc.getCurrentRevision(); Map<String, Object> properties2 = new HashMap<String, Object>(properties); properties2.put("tag", 4567); SavedRevision rev2 = rev1.createRevision(properties2); database.compact(); Document fetchedDoc = database.getDocument(doc.getId()); List<SavedRevision> revisions = fetchedDoc.getRevisionHistory(); for (SavedRevision revision : revisions) { if (revision.getId().equals(rev1)) { assertFalse(revision.arePropertiesAvailable()); } if (revision.getId().equals(rev2)) { assertTrue(revision.arePropertiesAvailable()); } } } public void testDocumentCache() throws Exception { Database db = startDatabase(); Document doc = db.createDocument(); UnsavedRevision rev1 = doc.createRevision(); Map<String, Object> rev1Properties = new HashMap<String, Object>(); rev1Properties.put("foo", "bar"); rev1.setUserProperties(rev1Properties); SavedRevision savedRev1 = rev1.save(); String documentId = savedRev1.getDocument().getId(); // getting the document puts it in cache Document docRev1 = db.getExistingDocument(documentId); UnsavedRevision rev2 = docRev1.createRevision(); Map<String, Object> rev2Properties = rev2.getProperties(); rev2Properties.put("foo", "baz"); rev2.setUserProperties(rev2Properties); rev2.save(); Document docRev2 = db.getExistingDocument(documentId); assertEquals("baz", docRev2.getProperty("foo")); } // - (void) test05_CreateRevisions in Database_Tests.m public void testCreateRevisions() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testCreateRevisions"); properties.put("tag", 1337); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); assertFalse(doc.isDeleted()); SavedRevision rev1 = doc.getCurrentRevision(); assertTrue(rev1.getId().startsWith("1-")); assertEquals(1, rev1.getSequence()); assertEquals(0, rev1.getAttachments().size()); // Test -createRevisionWithProperties: Map<String, Object> properties2 = new HashMap<String, Object>(properties); properties2.put("tag", 4567); SavedRevision rev2 = rev1.createRevision(properties2); assertNotNull("Put failed", rev2); assertTrue("Document revision ID is still " + doc.getCurrentRevisionId(), doc.getCurrentRevisionId().startsWith("2-")); assertEquals(rev2.getId(), doc.getCurrentRevisionId()); assertNotNull(rev2.arePropertiesAvailable()); assertEquals(rev2.getUserProperties(), properties2); assertEquals(rev2.getDocument(), doc); assertEquals(rev2.getProperty("_id"), doc.getId()); assertEquals(rev2.getProperty("_rev"), rev2.getId()); // Test -createRevision: UnsavedRevision newRev = rev2.createRevision(); assertNull(newRev.getId()); assertEquals(newRev.getParent(), rev2); assertEquals(newRev.getParentId(), rev2.getId()); assertEquals(doc.getCurrentRevision(), rev2); assertFalse(doc.isDeleted()); List<SavedRevision> listRevs = new ArrayList<SavedRevision>(); listRevs.add(rev1); listRevs.add(rev2); assertEquals(newRev.getRevisionHistory(), listRevs); assertEquals(newRev.getProperties(), rev2.getProperties()); assertEquals(newRev.getUserProperties(), rev2.getUserProperties()); Map<String, Object> userProperties = new HashMap<String, Object>(); userProperties.put("because", "NoSQL"); newRev.setUserProperties(userProperties); assertEquals(newRev.getUserProperties(), userProperties); Map<String, Object> expectProperties = new HashMap<String, Object>(); expectProperties.put("because", "NoSQL"); expectProperties.put("_id", doc.getId()); expectProperties.put("_rev", rev2.getId()); assertEquals(newRev.getProperties(), expectProperties); SavedRevision rev3 = newRev.save(); assertNotNull(rev3); assertEquals(rev3.getUserProperties(), newRev.getUserProperties()); } // - (void) test07_CreateNewRevisions in Database_Tests.m public void testCreateNewRevisions() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testCreateRevisions"); properties.put("tag", 1337); Database db = startDatabase(); Document doc = db.createDocument(); UnsavedRevision newRev = doc.createRevision(); Document newRevDocument = newRev.getDocument(); assertEquals(doc, newRevDocument); assertEquals(db, newRev.getDatabase()); assertNull(newRev.getParentId()); assertNull(newRev.getParent()); Map<String, Object> expectProperties = new HashMap<String, Object>(); expectProperties.put("_id", doc.getId()); assertEquals(expectProperties, newRev.getProperties()); assertTrue(!newRev.isDeletion()); assertEquals(newRev.getSequence(), 0); //ios support another approach to set properties:: //newRev.([@"testName"] = @"testCreateRevisions"; //newRev[@"tag"] = @1337; newRev.setUserProperties(properties); assertEquals(newRev.getUserProperties(), properties); SavedRevision rev1 = newRev.save(); assertNotNull("Save 1 failed", rev1); assertEquals(doc.getCurrentRevision(), rev1); assertNotNull(rev1.getId().startsWith("1-")); assertEquals(1, rev1.getSequence()); assertNull(rev1.getParentId()); assertNull(rev1.getParent()); newRev = rev1.createRevision(); newRevDocument = newRev.getDocument(); assertEquals(doc, newRevDocument); assertEquals(db, newRev.getDatabase()); assertEquals(rev1.getId(), newRev.getParentId()); assertEquals(rev1, newRev.getParent()); assertEquals(rev1.getProperties(), newRev.getProperties()); assertEquals(rev1.getUserProperties(), newRev.getUserProperties()); assertNotNull(!newRev.isDeletion()); // we can't add/modify one property as on ios. need to add separate method? // newRev[@"tag"] = @4567; properties.put("tag", 4567); newRev.setUserProperties(properties); SavedRevision rev2 = newRev.save(); assertNotNull("Save 2 failed", rev2); assertEquals(doc.getCurrentRevision(), rev2); assertNotNull(rev2.getId().startsWith("2-")); assertEquals(2, rev2.getSequence()); assertEquals(rev1.getId(), rev2.getParentId()); assertEquals(rev1, rev2.getParent()); assertNotNull("Document revision ID is still " + doc.getCurrentRevisionId(), doc.getCurrentRevisionId().startsWith("2-")); // Add a deletion/tombstone revision: newRev = doc.createRevision(); assertEquals(rev2.getId(), newRev.getParentId()); assertEquals(rev2, newRev.getParent()); newRev.setIsDeletion(true); SavedRevision rev3 = newRev.save(); assertNotNull("Save 3 failed", rev3); assertNotNull("Unexpected revID " + rev3.getId(), rev3.getId().startsWith("3-")); assertEquals(3, rev3.getSequence()); assertTrue(rev3.isDeletion()); assertTrue(doc.isDeleted()); assertNull(doc.getCurrentRevision()); List<SavedRevision> leafRevs = new ArrayList<SavedRevision>(); leafRevs.add(rev3); assertEquals(doc.getLeafRevisions(), leafRevs); db.getDocumentCount(); Document doc2 = db.getDocument(doc.getId()); assertEquals(doc, doc2); assertNull(db.getExistingDocument(doc.getId())); } //API_SaveMultipleDocuments on IOS //API_SaveMultipleUnsavedDocuments on IOS //API_DeleteMultipleDocuments commented on IOS // - (void) test09_DeleteDocument in Database_Tests.m public void testDeleteDocument() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testDeleteDocument"); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); assertTrue(!doc.isDeleted()); assertTrue(!doc.getCurrentRevision().isDeletion()); assertTrue(doc.delete()); assertTrue(doc.isDeleted()); assertNull(doc.getCurrentRevision()); } // - (void) test10_PurgeDocument in Database_Tests.m public void testPurgeDocument() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testPurgeDocument"); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); assertNotNull(doc); doc.purge(); Document redoc = db.getCachedDocument(doc.getId()); assertNull(redoc); } public void testDeleteDocumentViaTombstoneRevision() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "testDeleteDocument"); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); assertTrue(!doc.isDeleted()); assertTrue(!doc.getCurrentRevision().isDeletion()); Map<String, Object> props = new HashMap<String, Object>(doc.getProperties()); props.put("_deleted", true); SavedRevision deletedRevision = doc.putProperties(props); assertTrue(doc.isDeleted()); assertTrue(deletedRevision.isDeletion()); assertNull(doc.getCurrentRevision()); } // - (void) test12_AllDocuments in Database_Tests.m public void testAllDocuments() throws Exception { Database db = startDatabase(); int kNDocs = 5; createDocuments(db, kNDocs); // clear the cache so all documents/revisions will be re-fetched: db.clearDocumentCache(); Log.i(TAG, "----- all documents -----"); Query query = db.createAllDocumentsQuery(); //query.prefetch = YES; Log.i(TAG, "Getting all documents: " + query); QueryEnumerator rows = query.run(); assertEquals(rows.getCount(), kNDocs); int n = 0; for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); Log.i(TAG, " --> " + row); Document doc = row.getDocument(); assertNotNull("Couldn't get doc from query", doc); assertNotNull("QueryRow should have preloaded revision contents", doc.getCurrentRevision().arePropertiesAvailable()); Log.i(TAG, " Properties =" + doc.getProperties()); assertNotNull("Couldn't get doc properties", doc.getProperties()); assertEquals(doc.getProperty("testName"), "testDatabase"); n++; } assertEquals(n, kNDocs); } // - (void) test12_AllDocumentsPrefixMatchLevel in Database_Tests.m // - (void) test12_AllDocumentsBySequence in Database_Tests.m public void test12_AllDocumentsBySequence() throws Exception { Database db = startDatabase(); int kNDocs = 10; createDocuments(db, kNDocs); // clear the cache so all documents/revisions will be re-fetched: db.clearDocumentCache(); Query query = db.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.BY_SEQUENCE); QueryEnumerator rows = query.run(); long n = 0; for (QueryRow row : rows) { n++; Document doc = row.getDocument(); assertNotNull(doc); assertEquals(n, doc.getCurrentRevision().getSequence()); } assertEquals(n, kNDocs); query = db.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.BY_SEQUENCE); query.setStartKey(3); rows = query.run(); n = 2; for (QueryRow row : rows) { n++; Document doc = row.getDocument(); assertNotNull(doc); assertEquals(n, doc.getCurrentRevision().getSequence()); } assertEquals(kNDocs, n); query = db.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.BY_SEQUENCE); query.setEndKey(6); rows = query.run(); n = 0; for (QueryRow row : rows) { n++; Document doc = row.getDocument(); assertNotNull(doc); assertEquals(n, doc.getCurrentRevision().getSequence()); } assertEquals(6, n); query = db.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.BY_SEQUENCE); query.setStartKey(3); query.setEndKey(6); query.setInclusiveStart(false); query.setInclusiveEnd(false); rows = query.run(); n = 3; for (QueryRow row : rows) { n++; Document doc = row.getDocument(); assertNotNull(doc); assertEquals(n, doc.getCurrentRevision().getSequence()); } assertEquals(5, n); } // - (void) test13_LocalDocs in Database_Tests.m public void testLocalDocs() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("foo", "bar"); Database db = startDatabase(); Map<String, Object> props = db.getExistingLocalDocument("dock"); assertNull(props); assertNotNull("Couldn't put new local doc", db.putLocalDocument("dock", properties)); props = db.getExistingLocalDocument("dock"); assertEquals(props.get("foo"), "bar"); Map<String, Object> newProperties = new HashMap<String, Object>(); newProperties.put("FOOO", "BARRR"); assertNotNull("Couldn't update local doc", db.putLocalDocument("dock", newProperties)); props = db.getExistingLocalDocument("dock"); assertNull(props.get("foo")); assertEquals(props.get("FOOO"), "BARRR"); assertNotNull("Couldn't delete local doc", db.deleteLocalDocument("dock")); props = db.getExistingLocalDocument("dock"); assertNull(props); try{ db.deleteLocalDocument("dock"); fail("Should throw Exception"); }catch(CouchbaseLiteException ex){ assertEquals(Status.NOT_FOUND, ex.getCBLStatus().getCode()); } } // HISTORY public void testHistory() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("testName", "test06_History"); properties.put("tag", 1); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, properties); String rev1ID = doc.getCurrentRevisionId(); Log.i(TAG, "1st revision: " + rev1ID); assertNotNull("1st revision looks wrong: " + rev1ID, rev1ID.startsWith("1-")); assertEquals(doc.getUserProperties(), properties); properties = new HashMap<String, Object>(); properties.putAll(doc.getProperties()); properties.put("tag", 2); assertNotNull(!properties.equals(doc.getProperties())); assertNotNull(doc.putProperties(properties)); String rev2ID = doc.getCurrentRevisionId(); Log.i(TAG, "rev2ID" + rev2ID); assertNotNull("2nd revision looks wrong:" + rev2ID, rev2ID.startsWith("2-")); List<SavedRevision> revisions = doc.getRevisionHistory(); Log.i(TAG, "Revisions = " + revisions); assertEquals(revisions.size(), 2); SavedRevision rev1 = revisions.get(0); assertEquals(rev1.getId(), rev1ID); Map<String, Object> gotProperties = rev1.getProperties(); assertEquals(1, gotProperties.get("tag")); SavedRevision rev2 = revisions.get(1); assertEquals(rev2.getId(), rev2ID); assertEquals(rev2, doc.getCurrentRevision()); gotProperties = rev2.getProperties(); assertEquals(2, gotProperties.get("tag")); List<SavedRevision> tmp = new ArrayList<SavedRevision>(); tmp.add(rev2); assertEquals(doc.getConflictingRevisions(), tmp); assertEquals(doc.getLeafRevisions(), tmp); } public void testHistoryAfterDocDeletion() throws Exception { Map<String, Object> properties = new HashMap<String, Object>(); String docId = "testHistoryAfterDocDeletion"; properties.put("tag", 1); Database db = startDatabase(); Document doc = db.getDocument(docId); assertEquals(docId, doc.getId()); doc.putProperties(properties); String revID = doc.getCurrentRevisionId(); for (int i = 2; i < 6; i++) { properties.put("tag", i); properties.put("_rev", revID); doc.putProperties(properties); revID = doc.getCurrentRevisionId(); Log.i(TAG, i + " revision: " + revID); assertTrue("revision is not correct:" + revID + ", should be with prefix " + i + "-", revID.startsWith(String.valueOf(i) + "-")); assertEquals("Doc Id is not correct ", docId, doc.getId()); } // now delete the doc and clear it from the cache so we // make sure we are reading a fresh copy doc.delete(); database.clearDocumentCache(); // get doc from db with same ID as before, and the current rev should be null since the // last update was a deletion Document docPostDelete = db.getDocument(docId); assertNull(docPostDelete.getCurrentRevision()); // save a new revision properties = new HashMap<String, Object>(); properties.put("tag", 6); UnsavedRevision newRevision = docPostDelete.createRevision(); newRevision.setProperties(properties); SavedRevision newSavedRevision = newRevision.save(); // make sure the current revision of doc matches the rev we just saved assertEquals(newSavedRevision, docPostDelete.getCurrentRevision()); // make sure the rev id is 7- assertTrue(docPostDelete.getCurrentRevisionId().startsWith("7-")); } /** * in Database_Tests.m * - (void) test14_Conflict */ public void testConflict() throws Exception { Map<String, Object> prop = new HashMap<String, Object>(); prop.put("foo", "bar"); Database db = startDatabase(); Document doc = createDocumentWithProperties(db, prop); SavedRevision rev1 = doc.getCurrentRevision(); Map<String, Object> properties = new HashMap<String, Object>(); properties.putAll(doc.getProperties()); properties.put("tag", 2); SavedRevision rev2a = doc.putProperties(properties); properties = new HashMap<String, Object>(); properties.putAll(rev1.getProperties()); properties.put("tag", 3); UnsavedRevision newRev = rev1.createRevision(); newRev.setProperties(properties); boolean allowConflict = true; SavedRevision rev2b = newRev.save(allowConflict); assertNotNull("Failed to create a a conflict", rev2b); // NOTE: getConflictingRevisions() and getLeafRevisions() => order is not guaranteed List<SavedRevision> expectedConfRevs1 = new ArrayList<SavedRevision>(); expectedConfRevs1.add(rev2a); expectedConfRevs1.add(rev2b); List<SavedRevision> expectedConfRevs2 = new ArrayList<SavedRevision>(); expectedConfRevs2.add(rev2b); expectedConfRevs2.add(rev2a); List<SavedRevision> actualConfRevs = doc.getConflictingRevisions(); assertTrue(expectedConfRevs1.equals(actualConfRevs) || expectedConfRevs2.equals(actualConfRevs)); List<SavedRevision> actualLeafRevs = doc.getLeafRevisions(); assertTrue(expectedConfRevs1.equals(actualLeafRevs) || expectedConfRevs2.equals(actualLeafRevs)); SavedRevision defaultRev, otherRev; if (rev2a.getId().compareTo(rev2b.getId()) > 0) { defaultRev = rev2a; otherRev = rev2b; } else { defaultRev = rev2b; otherRev = rev2a; } assertEquals(doc.getCurrentRevision(), defaultRev); Query query = db.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.SHOW_CONFLICTS); QueryEnumerator rows = query.run(); assertEquals(rows.getCount(), 1); QueryRow row = rows.getRow(0); List<SavedRevision> revs = row.getConflictingRevisions(); assertEquals(revs.size(), 2); assertEquals(revs.get(0), defaultRev); assertEquals(revs.get(1), otherRev); } public void testCreateIdenticalParentContentRevisions() throws Exception { Document doc = database.createDocument(); SavedRevision rev = doc.createRevision().save(); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("foo", "bar"); UnsavedRevision unsavedRev1 = rev.createRevision(); unsavedRev1.setProperties(properties); SavedRevision savedRev1 = unsavedRev1.save(true); assertNotNull(savedRev1); UnsavedRevision unsavedRev2 = rev.createRevision(); unsavedRev2.setProperties(properties); SavedRevision savedRev2 = unsavedRev2.save(true); assertNotNull(savedRev2); assertEquals(savedRev1.getId(), savedRev2.getId()); List<SavedRevision> conflicts = doc.getConflictingRevisions(); assertEquals(1, conflicts.size()); Query query = database.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.ONLY_CONFLICTS); QueryEnumerator result = query.run(); assertNotNull(result); assertEquals(0, result.getCount()); } //ATTACHMENTS public void testAttachments() throws Exception, IOException { String attachmentName = "index.html"; String content = "This is a test attachment!"; Document doc = createDocWithAttachment(database, attachmentName, content); UnsavedRevision newRev = doc.getCurrentRevision().createRevision(); newRev.removeAttachment(attachmentName); SavedRevision rev4 = newRev.save(); assertNotNull(rev4); assertEquals(0, rev4.getAttachmentNames().size()); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/132 */ public void testUpdateDocWithAttachments() throws Exception, IOException { String attachmentName = "index.html"; String content = "This is a test attachment!"; Document doc = createDocWithAttachment(database, attachmentName, content); SavedRevision latestRevision = doc.getCurrentRevision(); Map<String, Object> propertiesUpdated = new HashMap<String, Object>(); propertiesUpdated.put("propertiesUpdated", "testUpdateDocWithAttachments"); UnsavedRevision newUnsavedRevision = latestRevision.createRevision(); newUnsavedRevision.setUserProperties(propertiesUpdated); SavedRevision newSavedRevision = newUnsavedRevision.save(); assertNotNull(newSavedRevision); assertEquals(1, newSavedRevision.getAttachmentNames().size()); Attachment fetched = doc.getCurrentRevision().getAttachment(attachmentName); byte[] attachmentBytes; InputStream is = fetched.getContent(); try { attachmentBytes = TextUtils.read(is); } finally { is.close(); } assertEquals(content, new String(attachmentBytes)); assertNotNull(fetched); } //CHANGE TRACKING public void testChangeTracking() throws Exception { final CountDownLatch doneSignal = new CountDownLatch(1); Database db = startDatabase(); db.addChangeListener(new Database.ChangeListener() { @Override public void changed(Database.ChangeEvent event) { doneSignal.countDown(); } }); createDocumentsAsync(db, 5); // We expect that the changes reported by the server won't be notified, because those // revisions are already cached in memory. boolean success = doneSignal.await(300, TimeUnit.SECONDS); assertTrue(success); assertEquals(5, db.getLastSequenceNumber()); } //VIEWS public void testCreateView() throws Exception { Database db = startDatabase(); View view = db.getView("vu"); assertNotNull(view); assertEquals(db, view.getDatabase()); assertEquals("vu", view.getName()); assertNull(view.getMap()); assertNull(view.getReduce()); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Log.w(TAG, "[testCreateView().map()] START"); Log.w(TAG, "[testCreateView().map()] key=" + document.get("sequence").toString()); emitter.emit(document.get("sequence"), null); //emitter.emit(document.get("sequence").toString(), null); Log.w(TAG, "[testCreateView().map()] END"); } }, "1"); //assertNotNull(view.getMap() != null); assertNotNull(view.getMap()); int kNDocs = 50; createDocuments(db, kNDocs); Query query = view.createQuery(); assertEquals(db, query.getDatabase()); query.setStartKey(23); query.setEndKey(33); QueryEnumerator rows = query.run(); assertNotNull(rows); assertEquals(11, rows.getCount()); int expectedKey = 23; for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); Object key = row.getKey(); if(key instanceof Double) key = ((Double)key).intValue(); assertEquals(expectedKey, key); assertEquals(expectedKey + 1, row.getSequenceNumber()); ++expectedKey; } } //API_RunSlowView commented on IOS public void testValidation() throws Exception { Database db = startDatabase(); db.setValidation("uncool", new Validator() { @Override public void validate(Revision newRevision, ValidationContext context) { { if (newRevision.getProperty("groovy") == null) { context.reject("uncool"); } } } }); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("groovy", "right on"); properties.put("foo", "bar"); Document doc = db.createDocument(); assertNotNull(doc.putProperties(properties)); properties = new HashMap<String, Object>(); properties.put("foo", "bar"); doc = db.createDocument(); try { assertNull(doc.putProperties(properties)); } catch (CouchbaseLiteException e) { //TODO assertEquals(e.getCBLStatus().getCode(), Status.FORBIDDEN); // assertEquals(e.getLocalizedMessage(), "forbidden: uncool"); //TODO: Not hooked up yet } } public void testViewWithLinkedDocs() throws Exception { Database db = startDatabase(); int kNDocs = 50; Document[] docs = new Document[50]; String lastDocID = ""; for (int i = 0; i < kNDocs; i++) { Map<String, Object> properties = new HashMap<String, Object>(); properties.put("sequence", i); properties.put("prev", lastDocID); Document doc = createDocumentWithProperties(db, properties); docs[i] = doc; lastDocID = doc.getId(); } Query query = db.slowQuery(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), new Object[]{"_id", document.get("prev")}); } }); query.setStartKey(23); query.setEndKey(33); query.setPrefetch(true); QueryEnumerator rows = query.run(); assertNotNull(rows); assertEquals(rows.getCount(), 11); int rowNumber = 23; for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); assertEquals(rowNumber, ((Number)row.getKey()).intValue()); Document prevDoc = docs[rowNumber]; assertEquals(row.getDocumentId(), prevDoc.getId()); assertEquals(row.getDocument(), prevDoc); ++rowNumber; } } public void testLiveQueryRun() throws Exception { runLiveQuery("run"); } public void testLiveQueryStart() throws Exception { runLiveQuery("start"); } public void testLiveQueryStartWaitForRows() throws Exception { runLiveQuery("startWaitForRows"); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/84 */ public void testLiveQueryStop() throws Exception { final int kNDocs = 50; final CountDownLatch doneSignal = new CountDownLatch(1); final Database db = startDatabase(); // run a live query View view = db.getView("vu"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), null); } }, "1"); final LiveQuery query = view.createQuery().toLiveQuery(); final AtomicInteger atomicInteger = new AtomicInteger(0); // install a change listener which decrements countdown latch when it sees a new // key from the list of expected keys final LiveQuery.ChangeListener changeListener = new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { Log.d(TAG, "changed called, atomicInteger.incrementAndGet"); atomicInteger.incrementAndGet(); assertNull(event.getError()); if (event.getRows().getCount() == kNDocs) { doneSignal.countDown(); } } }; query.addChangeListener(changeListener); // create the docs that will cause the above change listener to decrement countdown latch Log.d(Database.TAG, "testLiveQueryStop: createDocumentsAsync()"); createDocumentsAsync(db, kNDocs); Log.d(Database.TAG, "testLiveQueryStop: calling query.start()"); query.start(); // wait until the livequery is called back with kNDocs docs Log.d(Database.TAG, "testLiveQueryStop: waiting for doneSignal"); boolean success = doneSignal.await(45, TimeUnit.SECONDS); assertTrue(success); Log.d(Database.TAG, "testLiveQueryStop: waiting for query.stop()"); query.stop(); // after stopping the query, we should not get any more livequery callbacks, even // if we add more docs to the database and pause (to give time for potential callbacks) int numTimesCallbackCalled = atomicInteger.get(); Log.d(Database.TAG, "testLiveQueryStop: numTimesCallbackCalled is: " + numTimesCallbackCalled + ". Now adding docs"); for (int i = 0; i < 10; i++) { createDocuments(db, 1); Log.d(Database.TAG, "testLiveQueryStop: add a document. atomicInteger.get(): " + atomicInteger.get()); assertEquals(numTimesCallbackCalled, atomicInteger.get()); Thread.sleep(100); } assertEquals(numTimesCallbackCalled, atomicInteger.get()); } public void testLiveQueryRestart() throws Exception { // kick something off that will s } public void runLiveQuery(String methodNameToCall) throws Exception { final Database db = startDatabase(); final CountDownLatch doneSignal = new CountDownLatch(11); // 11 corresponds to startKey=23; endKey=33 // run a live query View view = db.getView("vu"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), null); } }, "1"); final LiveQuery query = view.createQuery().toLiveQuery(); query.setStartKey(23); query.setEndKey(33); Log.i(TAG, "Created " + query); // these are the keys that we expect to see in the livequery change listener callback final Set<Integer> expectedKeys = new HashSet<Integer>(); for (int i = 23; i < 34; i++) { expectedKeys.add(i); } // install a change listener which decrements countdown latch when it sees a new // key from the list of expected keys final LiveQuery.ChangeListener changeListener = new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { QueryEnumerator rows = event.getRows(); for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); if (expectedKeys.contains(((Number)row.getKey()).intValue())) { expectedKeys.remove(((Number)row.getKey()).intValue()); doneSignal.countDown(); } } } }; query.addChangeListener(changeListener); // create the docs that will cause the above change listener to decrement countdown latch int kNDocs = 50; createDocumentsAsync(db, kNDocs); if (methodNameToCall.equals("start")) { // start the livequery running asynchronously query.start(); } else if (methodNameToCall.equals("startWaitForRows")) { query.start(); query.waitForRows(); } else { assertNull(query.getRows()); query.run(); // this will block until the query completes assertNotNull(query.getRows()); } // wait for the doneSignal to be finished boolean success = doneSignal.await(300, TimeUnit.SECONDS); assertTrue("Done signal timed out, live query never ran", success); // stop the livequery since we are done with it query.removeChangeListener(changeListener); query.stop(); // Workaround for https://github.com/couchbase/couchbase-lite-android/issues/613 try { Thread.sleep(1 * 1000); } catch (Exception e) { } } public void testAsyncViewQuery() throws Exception { final CountDownLatch doneSignal = new CountDownLatch(1); final Database db = startDatabase(); View view = db.getView("vu"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), null); } }, "1"); int kNDocs = 50; createDocuments(db, kNDocs); Query query = view.createQuery(); query.setStartKey(23); query.setEndKey(33); query.runAsync(new Query.QueryCompleteListener() { @Override public void completed(QueryEnumerator rows, Throwable error) { Log.i(TAG, "Async query finished!"); assertNotNull(rows); assertNull(error); assertEquals(rows.getCount(), 11); int expectedKey = 23; for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); assertEquals(row.getDocument().getDatabase(), db); Object key = row.getKey(); if (key instanceof Double) key = ((Double) key).intValue(); assertEquals(expectedKey, key); ++expectedKey; } doneSignal.countDown(); } }); Log.i(TAG, "Waiting for async query to finish..."); boolean success = doneSignal.await(120, TimeUnit.SECONDS); assertTrue("Done signal timed out, async query never ran", success); } /** * Make sure that a database's map/reduce functions are shared with the shadow database instance * running in the background server. */ public void testSharedMapBlocks() throws Exception { Manager mgr = createManager(getTestContext("API_SharedMapBlocks", true)); Database db = mgr.getDatabase("db"); db.open(); db.setFilter("phil", new ReplicationFilter() { @Override public boolean filter(SavedRevision revision, Map<String, Object> params) { return true; } }); db.setValidation("val", new Validator() { @Override public void validate(Revision newRevision, ValidationContext context) { } }); View view = db.getView("view"); boolean ok = view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return null; } }, "1" ); assertNotNull("Couldn't set map/reduce", ok); final Mapper map = view.getMap(); final Reducer reduce = view.getReduce(); final ReplicationFilter filter = db.getFilter("phil"); final Validator validation = db.getValidation("val"); Future result = mgr.runAsync("db", new AsyncTask() { @Override public void run(Database database) { assertNotNull(database); View serverView = database.getExistingView("view"); assertNotNull(serverView); assertEquals(database.getFilter("phil"), filter); assertEquals(database.getValidation("val"), validation); assertEquals(serverView.getMap(), map); assertEquals(serverView.getReduce(), reduce); } }); result.get(); // blocks until async task has run db.close(); mgr.close(); } public void testChangeUUID() throws Exception { Manager mgr = createManager(getTestContext("ChangeUUID", true)); Database db = mgr.getDatabase("db"); db.open(); String pub = db.publicUUID(); String priv = db.privateUUID(); assertTrue(pub.length() > 10); assertTrue(priv.length() > 10); assertTrue("replaceUUIDs failed", db.replaceUUIDs()); assertFalse(pub.equals(db.publicUUID())); assertFalse(priv.equals(db.privateUUID())); mgr.close(); } /** * https://github.com/couchbase/couchbase-lite-android/issues/220 */ public void testMultiDocumentUpdate() throws Exception { final int numberOfDocuments = 10; final int numberOfUpdates = 10; final Document[] docs = new Document[numberOfDocuments]; for (int j = 0; j < numberOfDocuments; j++) { Map<String, Object> prop = new HashMap<String, Object>(); prop.put("foo", "bar"); prop.put("toogle", true); Document document = createDocumentWithProperties(database, prop); docs[j] = document; } final AtomicInteger numDocsUpdated = new AtomicInteger(0); final AtomicInteger numExceptions = new AtomicInteger(0); for (int j = 0; j < numberOfDocuments; j++) { Document doc = docs[j]; for (int k = 0; k < numberOfUpdates; k++) { Map<String, Object> contents = new HashMap(doc.getProperties()); Boolean wasChecked = (Boolean) contents.get("toogle"); //toggle value of check property contents.put("toogle", !wasChecked); try { doc.putProperties(contents); numDocsUpdated.incrementAndGet(); } catch (CouchbaseLiteException cblex) { Log.e(TAG, "Document update failed", cblex); numExceptions.incrementAndGet(); } } } assertEquals(numberOfDocuments * numberOfUpdates, numDocsUpdated.get()); assertEquals(0, numExceptions.get()); } /** * https://github.com/couchbase/couchbase-lite-android/issues/220 */ public void testMultiDocumentUpdateInTransaction() throws Exception { final int numberOfDocuments = 10; final int numberOfUpdates = 10; final Document[] docs = new Document[numberOfDocuments]; database.runInTransaction(new TransactionalTask() { @Override public boolean run() { for (int j = 0; j < numberOfDocuments; j++) { Map<String, Object> prop = new HashMap<String, Object>(); prop.put("foo", "bar"); prop.put("toogle", true); Document document = createDocumentWithProperties(database, prop); docs[j] = document; } return true; } }); final AtomicInteger numDocsUpdated = new AtomicInteger(0); final AtomicInteger numExceptions = new AtomicInteger(0); database.runInTransaction(new TransactionalTask() { @Override public boolean run() { for (int j = 0; j < numberOfDocuments; j++) { Document doc = docs[j]; SavedRevision lastSavedRevision = null; for (int k = 0; k < numberOfUpdates; k++) { if (lastSavedRevision != null) { assertEquals(lastSavedRevision.getId(), doc.getCurrentRevisionId()); } Map<String, Object> contents = new HashMap(doc.getProperties()); Document docLatest = database.getDocument(doc.getId()); Boolean wasChecked = (Boolean) contents.get("toogle"); //toggle value of check property contents.put("toogle", !wasChecked); try { lastSavedRevision = doc.putProperties(contents); numDocsUpdated.incrementAndGet(); } catch (CouchbaseLiteException cblex) { Log.e(TAG, "Document update failed", cblex); numExceptions.incrementAndGet(); } } } return true; } }); assertEquals(numberOfDocuments * numberOfUpdates, numDocsUpdated.get()); assertEquals(0, numExceptions.get()); } /** * https://github.com/couchbase/couchbase-lite-android/issues/685#issuecomment-158917064 */ public void testRevPruning() throws IOException, CouchbaseLiteException { final int DOC_COUNT = 10; final int REV_COUNT = 20; final int MAX_REV_TREE_DEPTH = 10; database.setMaxRevTreeDepth(MAX_REV_TREE_DEPTH); for (int j = 0; j < DOC_COUNT; j++) { final Document document = database.createDocument(); for (int i = 0; i < REV_COUNT; i++) { final String value = "rev " + i; document.update(new Document.DocumentUpdater() { @Override public boolean update(final UnsavedRevision newRevision) { newRevision.getProperties().put("key", value); return true; } }); } assertEquals(MAX_REV_TREE_DEPTH, document.getRevisionHistory().size()); } database.compact(); Query query = database.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.ALL_DOCS); QueryEnumerator rowEnum = query.run(); while (rowEnum.hasNext()) { QueryRow row = rowEnum.next(); assertEquals(MAX_REV_TREE_DEPTH, row.getDocument().getRevisionHistory().size()); } } public void testWinningRevByDeleteDocument() throws Exception { Map<String, Object> props = new HashMap<String, Object>(); props.put("testName", "testWinningRevByDeleteDocument"); props.put("tag", 1); // 1st revision Document doc = database.createDocument(); UnsavedRevision newRev = doc.createRevision(); newRev.setUserProperties(props); SavedRevision rev1 = newRev.save(); assertNotNull(rev1); assertTrue(rev1.getId().startsWith("1-")); assertEquals(1, rev1.getSequence()); // 2nd revision props.put("tag", 2); newRev = rev1.createRevision(); newRev.setUserProperties(props); SavedRevision rev2 = newRev.save(); assertNotNull(rev2); assertTrue(rev2.getId().startsWith("2-")); assertEquals(2, rev2.getSequence()); // 3rd revision props.put("tag", 3); newRev = rev2.createRevision(); newRev.setUserProperties(props); SavedRevision rev3 = newRev.save(); assertNotNull(rev3); assertTrue(rev3.getId().startsWith("3-")); assertEquals(3, rev3.getSequence()); // 4th revision with deleted SavedRevision rev4 = doc.update(new Document.DocumentUpdater() { @Override public boolean update(UnsavedRevision newRevision) { newRevision.setIsDeletion(true); return true; } }); assertNotNull(rev4); assertTrue(rev4.getId().startsWith("4-")); assertEquals(4, rev4.getSequence()); assertTrue(rev4.isDeletion()); assertTrue(doc.isDeleted()); assertNull(doc.getCurrentRevision()); } }