/** * 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.javascript.JavaScriptReplicationFilterCompiler; import com.couchbase.lite.javascript.JavaScriptViewCompiler; import com.couchbase.lite.mockserver.MockChangesFeed; import com.couchbase.lite.mockserver.MockCheckpointGet; import com.couchbase.lite.mockserver.MockCheckpointPut; import com.couchbase.lite.mockserver.MockDispatcher; import com.couchbase.lite.mockserver.MockDocumentAllDocs; import com.couchbase.lite.mockserver.MockDocumentBulkGet; import com.couchbase.lite.mockserver.MockDocumentGet; import com.couchbase.lite.mockserver.MockHelper; import com.couchbase.lite.replicator.Replication; import com.couchbase.lite.router.URLConnection; import com.couchbase.lite.util.Log; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; public class RouterTest extends LiteTestCaseWithDB { public static final String TAG = "Router"; // - (void) test_Server in Router_Tests.m public void test_Server() { Map<String, Object> responseBody = new HashMap<String, Object>(); responseBody.put("CBLite", "Welcome"); responseBody.put("couchdb", "Welcome"); responseBody.put("version", com.couchbase.lite.router.Router.getVersionString()); send("GET", "/", Status.OK, responseBody); Map<String, Object> session = new HashMap<String, Object>(); Map<String, Object> userCtx = new HashMap<String, Object>(); List<String> roles = new ArrayList<String>(); roles.add("_admin"); session.put("ok", true); userCtx.put("name", null); userCtx.put("roles", roles); session.put("userCtx", userCtx); send("GET", "/_session", Status.OK, session); List<String> allDbs = new ArrayList<String>(); allDbs.add("cblite-test"); send("GET", "/_all_dbs", Status.OK, allDbs); send("GET", "/non-existant", Status.NOT_FOUND, null); send("GET", "/BadName", Status.BAD_REQUEST, null); Map<String, Object> expectedBody = new HashMap<String, Object>(); expectedBody.put("error", "method_not_allowed"); expectedBody.put("status", 405); send("PUT", "/", Status.METHOD_NOT_ALLOWED, expectedBody); send("POST", "/", Status.METHOD_NOT_ALLOWED, expectedBody); } // - (void) test_Databases in Router_Tests.m public void test_Databases() { send("PUT", "/database", Status.CREATED, null); Map entries = new HashMap<String, Map<String, Object>>(); entries.put("results", new ArrayList<Object>()); entries.put("last_seq", 0); send("GET", "/database/_changes?feed=normal&heartbeat=300000&style=all_docs", Status.OK, entries); Map<String, Object> dbInfo = (Map<String, Object>) send("GET", "/database", Status.OK, null); assertEquals(6, dbInfo.size()); assertEquals(0, dbInfo.get("doc_count")); assertEquals(0, dbInfo.get("update_seq")); assertTrue((Integer) dbInfo.get("disk_size") > 8000); assertEquals("database", dbInfo.get("db_name")); // following line of test is problematic. Because of System.currentTimeMillis()?? // assertTrue(System.currentTimeMillis() * 1000 > (Long) dbInfo.get("instance_start_time")); assertTrue(dbInfo.containsKey("db_uuid")); send("PUT", "/database", Status.DUPLICATE, null); send("PUT", "/database2", Status.CREATED, null); List<String> allDbs = new ArrayList<String>(); allDbs.add("cblite-test"); allDbs.add("database"); allDbs.add("database2"); send("GET", "/_all_dbs", Status.OK, allDbs); dbInfo = (Map<String, Object>) send("GET", "/database2", Status.OK, null); assertEquals("database2", dbInfo.get("db_name")); send("DELETE", "/database2", Status.OK, null); allDbs.remove("database2"); send("GET", "/_all_dbs", Status.OK, allDbs); send("PUT", "/database%2Fwith%2Fslashes", Status.CREATED, null); dbInfo = (Map<String, Object>) send("GET", "/database%2Fwith%2Fslashes", Status.OK, null); assertEquals("database/with/slashes", dbInfo.get("db_name")); } public void testDocWithAttachment() throws IOException { send("PUT", "/db", Status.CREATED, null); Map<String, Object> attachment = new HashMap<String, Object>(); attachment.put("content_type", "text/plain"); attachment.put("data", "SW5saW5lIHRleHQgc3RyaW5nIGNyZWF0ZWQgYnkgY2JsaXRlIGZ1bmN0aW9uYWwgdGVzdA=="); Map<String, Object> attachments = new HashMap<String, Object>(); attachments.put("inline.txt", attachment); String inlineTextString = "Inline text string created by cblite functional test"; Map<String, Object> docWithAttachment = new HashMap<String, Object>(); docWithAttachment.put("_id", "docWithAttachment"); docWithAttachment.put("text", inlineTextString); docWithAttachment.put("_attachments", attachments); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/docWithAttachment", docWithAttachment, Status.CREATED, null); Map expChanges = new HashMap<String, Map<String, Object>>(); List changesResults = new ArrayList(); Map docChanges = new HashMap<String, Object>(); docChanges.put("id", "docWithAttachment"); docChanges.put("seq", 1); List lChanges = new ArrayList<Map<String, Object>>(); HashMap mChanges = new HashMap<String, Object>(); mChanges.put("rev", result.get("rev")); lChanges.add(mChanges); docChanges.put("changes", lChanges); changesResults.add(docChanges); expChanges.put("results", changesResults); expChanges.put("last_seq", 1); send("GET", "/db/_changes?feed=normal&heartbeat=300000&style=all_docs", Status.OK, expChanges); result = (Map<String, Object>) send("GET", "/db/docWithAttachment", Status.OK, null); Map<String, Object> attachmentsResult = (Map<String, Object>) result.get("_attachments"); Map<String, Object> attachmentResult = (Map<String, Object>) attachmentsResult.get("inline.txt"); // there should be either a content_type or content-type field. //https://github.com/couchbase/couchbase-lite-android-core/issues/12 //content_type becomes null for attachments in responses, should be as set in Content-Type String contentTypeField = (String) attachmentResult.get("content_type"); assertTrue(attachmentResult.containsKey("content_type")); assertNotNull(contentTypeField); // no Accept URLConnection conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", null, null); String contentType = conn.getHeaderField("Content-Type"); assertNotNull(contentType); assertTrue(contentType.contains("text/plain")); InputStream is = conn.getInputStream(); String responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); // With Accept: text/plain Map<String, String> headers = new HashMap<String, String>(); headers.put("Accept", "text/plain"); conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", headers, null); contentType = conn.getHeaderField("Content-Type"); assertNotNull(contentType); assertTrue(contentType.contains("text/plain")); is = conn.getInputStream(); responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); // With Accept: application/json headers.put("Accept", "application/json"); conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", headers, null); assertEquals(Status.NOT_ACCEPTABLE, conn.getResponseCode()); is = conn.getInputStream(); Map<String, Object> body = Manager.getObjectMapper().readValue(is, Map.class); is.close(); assertEquals(406, body.get("status")); assertEquals("not_acceptable", body.get("error")); // With Accept: image/webp,image/*,*/*;q=0.8 headers.put("Accept", "image/webp,image/*,*/*;q=0.8"); conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", headers, null); contentType = conn.getHeaderField("Content-Type"); assertNotNull(contentType); assertTrue(contentType.contains("text/plain")); is = conn.getInputStream(); responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); // with attachments=true query parameter result = (Map<String, Object>) send("GET", "/db/docWithAttachment?attachments=true", Status.OK, null); assertNotNull(result); assertNotNull(result.get("_attachments")); Map<String, Object> att = (Map<String, Object>) ((Map<String, Object>) result.get("_attachments")).get("inline.txt"); assertNotNull(att); assertNotNull(att.get("data")); assertTrue(((String) att.get("data")).length() > 0); assertFalse(att.containsKey("stub")); // with attachments=true query parameter with an Accept value headers.put("Accept", "application/json"); result = (Map<String, Object>) send("GET", "/db/docWithAttachment?attachments=true", headers, Status.OK, null); assertNotNull(result); assertNotNull(result.get("_attachments")); att = (Map<String, Object>) ((Map<String, Object>) result.get("_attachments")).get("inline.txt"); assertNotNull(att); assertNotNull(att.get("data")); assertTrue(((String) att.get("data")).length() > 0); assertFalse(att.containsKey("stub")); // with attachments=true query parameter with an invalid Accept value headers.put("Accept", "applicatio/json"); result = (Map<String, Object>) send("GET", "/db/docWithAttachment?attachments=true", headers, Status.NOT_ACCEPTABLE, null); assertNotNull(result); assertEquals(406, result.get("status")); assertEquals("not_acceptable", result.get("error")); // without attachments=true query parameter result = (Map<String, Object>) send("GET", "/db/docWithAttachment", Status.OK, null); assertNotNull(result); assertNotNull(result.get("_attachments")); att = (Map<String, Object>) ((Map<String, Object>) result.get("_attachments")).get("inline.txt"); assertNotNull(att); assertNotNull(att.get("stub")); assertFalse(att.containsKey("data")); } public void testDocWithAttachmentNoContentType() throws IOException { send("PUT", "/db", Status.CREATED, null); Map<String, Object> attachment = new HashMap<String, Object>(); attachment.put("data", "SW5saW5lIHRleHQgc3RyaW5nIGNyZWF0ZWQgYnkgY2JsaXRlIGZ1bmN0aW9uYWwgdGVzdA=="); Map<String, Object> attachments = new HashMap<String, Object>(); attachments.put("inline.txt", attachment); String inlineTextString = "Inline text string created by cblite functional test"; Map<String, Object> docWithAttachment = new HashMap<String, Object>(); docWithAttachment.put("_id", "docWithAttachment"); docWithAttachment.put("text", inlineTextString); docWithAttachment.put("_attachments", attachments); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/docWithAttachment", docWithAttachment, Status.CREATED, null); // no Accept URLConnection conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", null, null); assertNull(conn.getHeaderField("Content-Type")); InputStream is = conn.getInputStream(); String responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); // With Accept: text/plain Map<String, String> headers = new HashMap<String, String>(); headers.put("Accept", "text/plain"); conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", headers, null); assertNull(conn.getHeaderField("Content-Type")); is = conn.getInputStream(); responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); // With Accept: */* headers = new HashMap<String, String>(); headers.put("Accept", "*/*"); conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", headers, null); assertNull(conn.getHeaderField("Content-Type")); is = conn.getInputStream(); responseString = IOUtils.toString(is, "UTF-8"); is.close(); assertTrue(responseString.contains(inlineTextString)); } private Map<String, Object> valueMapWithRev(String revId) { Map<String, Object> value = valueMapWithRevNoConflictArray(revId); value.put("_conflicts", new ArrayList<String>()); return value; } private Map<String, Object> valueMapWithRevNoConflictArray(String revId) { Map<String, Object> value = new HashMap<String, Object>(); value.put("rev", revId); return value; } // - (void) test_Docs in Router_Tests.m public void test_Docs() { send("PUT", "/db", Status.CREATED, null); // PUT: Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); assertTrue(revID.startsWith("1-")); // PUT to update: doc1.put("message", "goodbye"); doc1.put("_rev", revID); result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); Log.v(TAG, "PUT returned %s", result); revID = (String) result.get("rev"); assertTrue(revID.startsWith("2-")); doc1.put("_id", "doc1"); doc1.put("_rev", revID); result = (Map<String, Object>) send("GET", "/db/doc1", Status.OK, doc1); // Add more docs: Map<String, Object> docX = new HashMap<String, Object>(); docX.put("message", "hello"); result = (Map<String, Object>) sendBody("PUT", "/db/doc3", docX, Status.CREATED, null); String revID3 = (String) result.get("rev"); result = (Map<String, Object>) sendBody("PUT", "/db/doc2", docX, Status.CREATED, null); String revID2 = (String) result.get("rev"); // _all_docs: result = (Map<String, Object>) send("GET", "/db/_all_docs", Status.OK, null); assertEquals(3, result.get("total_rows")); assertEquals(0, result.get("offset")); Map<String, Object> value1 = valueMapWithRev(revID); Map<String, Object> value2 = valueMapWithRev(revID2); Map<String, Object> value3 = valueMapWithRev(revID3); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("id", "doc1"); row1.put("key", "doc1"); row1.put("value", value1); row1.put("doc", null); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("id", "doc2"); row2.put("key", "doc2"); row2.put("value", value2); row2.put("doc", null); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("id", "doc3"); row3.put("key", "doc3"); row3.put("value", value3); row3.put("doc", null); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); expectedRows.add(row1); expectedRows.add(row2); expectedRows.add(row3); List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get("rows"); assertEquals(expectedRows, rows); // DELETE: result = (Map<String, Object>) send("DELETE", String.format(Locale.ENGLISH, "/db/doc1?rev=%s", revID), Status.OK, null); revID = (String) result.get("rev"); assertTrue(revID.startsWith("3-")); send("GET", "/db/doc1", Status.NOT_FOUND, null); // _changes: List<Object> changes1 = new ArrayList<Object>(); changes1.add(valueMapWithRevNoConflictArray(revID)); List<Object> changes2 = new ArrayList<Object>(); changes2.add(valueMapWithRevNoConflictArray(revID2)); List<Object> changes3 = new ArrayList<Object>(); changes3.add(valueMapWithRevNoConflictArray(revID3)); Map<String, Object> result1 = new HashMap<String, Object>(); result1.put("id", "doc1"); result1.put("seq", 5); result1.put("deleted", true); result1.put("changes", changes1); Map<String, Object> result2 = new HashMap<String, Object>(); result2.put("id", "doc2"); result2.put("seq", 4); result2.put("changes", changes2); Map<String, Object> result3 = new HashMap<String, Object>(); result3.put("id", "doc3"); result3.put("seq", 3); result3.put("changes", changes3); List<Object> results = new ArrayList<Object>(); results.add(result3); results.add(result2); results.add(result1); Map<String, Object> expectedChanges = new HashMap<String, Object>(); expectedChanges.put("last_seq", 5); expectedChanges.put("results", results); send("GET", "/db/_changes", Status.OK, expectedChanges); // _changes with ?since: results.remove(result3); results.remove(result2); expectedChanges.put("results", results); send("GET", "/db/_changes?since=4", Status.OK, expectedChanges); Map<String, Object> body = new HashMap<String, Object>(); body.put("since", 4); sendBody("POST", "/db/_changes", body, Status.OK, expectedChanges); Map<String, Object> expectedBody = new HashMap<String, Object>(); expectedBody.put("error", "method_not_allowed"); expectedBody.put("status", 405); // 405 - PUT /{db}/_changes is not supported sendBody("PUT", "/db/_changes", body, Status.METHOD_NOT_ALLOWED, expectedBody); // 405 - DELETE /{db}/_changes is not supported send("DELETE", "/db/_changes", Status.METHOD_NOT_ALLOWED, expectedBody); results.remove(result1); expectedChanges.put("results", results); send("GET", "/db/_changes?since=5", Status.OK, expectedChanges); body.put("since", 5); sendBody("POST", "/db/_changes", body, Status.OK, expectedChanges); // Put with _deleted to delete a doc: Log.d(TAG, "Put with _deleted to delete a doc"); send("GET", "/db/doc5", Status.NOT_FOUND, null); Map<String, Object> doc5 = new HashMap<String, Object>(); doc5.put("message", "hello5"); Map<String, Object> resultDoc5 = (Map<String, Object>) sendBody("PUT", "/db/doc5", doc5, Status.CREATED, null); String revIdDoc5 = (String) resultDoc5.get("rev"); assertTrue(revIdDoc5.startsWith("1-")); doc5.put("_deleted", true); doc5.put("_rev", revIdDoc5); doc5.put("_id", "doc5"); result = (Map<String, Object>) sendBody("PUT", "/db/doc5", doc5, Status.OK, null); send("GET", "/db/doc5", Status.NOT_FOUND, null); Log.d(TAG, "Finished put with _deleted to delete a doc"); } // - (void) test_LocalDocs in Router_Tests.m public void test_LocalDocs() { send("PUT", "/db", Status.CREATED, null); // PUT a local doc: Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/_local/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); assertTrue(revID.startsWith("1-")); // GET it: doc1.put("_id", "_local/doc1"); doc1.put("_rev", revID); result = (Map<String, Object>) send("GET", "/db/_local/doc1", Status.OK, doc1); // Local doc should not appear in _changes feed: Map<String, Object> expectedChanges = new HashMap<String, Object>(); expectedChanges.put("last_seq", 0); expectedChanges.put("results", new ArrayList<Object>()); send("GET", "/db/_changes", Status.OK, expectedChanges); sendBody("POST", "/db/_changes", new HashMap<String, Object>(), Status.OK, expectedChanges); } // - (void) test_AllDocs in Router_Tests.m public void test_AllDocs() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> result; Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); Map<String, Object> doc3 = new HashMap<String, Object>(); doc3.put("message", "bonjour"); result = (Map<String, Object>) sendBody("PUT", "/db/doc3", doc3, Status.CREATED, null); String revID3 = (String) result.get("rev"); Map<String, Object> doc2 = new HashMap<String, Object>(); doc2.put("message", "guten tag"); result = (Map<String, Object>) sendBody("PUT", "/db/doc2", doc2, Status.CREATED, null); String revID2 = (String) result.get("rev"); // _all_docs: result = (Map<String, Object>) send("GET", "/db/_all_docs", Status.OK, null); assertEquals(3, result.get("total_rows")); assertEquals(0, result.get("offset")); Map<String, Object> value1 = valueMapWithRev(revID); Map<String, Object> value2 = valueMapWithRev(revID2); Map<String, Object> value3 = valueMapWithRev(revID3); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("id", "doc1"); row1.put("key", "doc1"); row1.put("value", value1); row1.put("doc", null); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("id", "doc2"); row2.put("key", "doc2"); row2.put("value", value2); row2.put("doc", null); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("id", "doc3"); row3.put("key", "doc3"); row3.put("value", value3); row3.put("doc", null); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); expectedRows.add(row1); expectedRows.add(row2); expectedRows.add(row3); List<Map<String, Object>> rows = (List<Map<String, Object>>) result.get("rows"); assertEquals(expectedRows, rows); // ?include_docs: result = (Map<String, Object>) send("GET", "/db/_all_docs?include_docs=true", Status.OK, null); assertEquals(3, result.get("total_rows")); assertEquals(0, result.get("offset")); doc1.put("_id", "doc1"); doc1.put("_rev", revID); row1.put("doc", doc1); doc2.put("_id", "doc2"); doc2.put("_rev", revID2); row2.put("doc", doc2); doc3.put("_id", "doc3"); doc3.put("_rev", revID3); row3.put("doc", doc3); List<Map<String, Object>> expectedRowsWithDocs = new ArrayList<Map<String, Object>>(); expectedRowsWithDocs.add(row1); expectedRowsWithDocs.add(row2); expectedRowsWithDocs.add(row3); rows = (List<Map<String, Object>>) result.get("rows"); assertEquals(expectedRowsWithDocs, rows); } // - (void) test_Views in Router_Tests.m public void test_Views() throws CouchbaseLiteException { send("PUT", "/db", Status.CREATED, null); // PUT: Map<String, Object> result; Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); Map<String, Object> doc3 = new HashMap<String, Object>(); doc3.put("message", "bonjour"); result = (Map<String, Object>) sendBody("PUT", "/db/doc3", doc3, Status.CREATED, null); String revID3 = (String) result.get("rev"); Map<String, Object> doc2 = new HashMap<String, Object>(); doc2.put("message", "guten tag"); result = (Map<String, Object>) sendBody("PUT", "/db/doc2", doc2, Status.CREATED, null); String revID2 = (String) result.get("rev"); Database db = manager.getDatabase("db"); View view = db.getView("design/view"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("message"), null); } }, null, "1"); // Build up our expected result Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("id", "doc1"); row1.put("key", "hello"); row1.put("doc", null); row1.put("value", null); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("id", "doc2"); row2.put("key", "guten tag"); row2.put("doc", null); row2.put("value", null); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("id", "doc3"); row3.put("key", "bonjour"); row3.put("doc", null); row3.put("value", null); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); expectedRows.add(row3); expectedRows.add(row2); expectedRows.add(row1); Map<String, Object> expectedResult = new HashMap<String, Object>(); expectedResult.put("offset", 0); expectedResult.put("total_rows", 3); expectedResult.put("rows", expectedRows); // Query the view and check the result: send("GET", "/db/_design/design/_view/view", Status.OK, expectedResult); // Check the ETag: URLConnection conn = sendRequest("GET", "/db/_design/design/_view/view", null, null); String etag = conn.getHeaderField("Etag"); assertEquals(String.format(Locale.ENGLISH, "\"%d\"", view.getLastSequenceIndexed()), etag); // Try a conditional GET: Map<String, String> headers = new HashMap<String, String>(); headers.put("If-None-Match", etag); conn = sendRequest("GET", "/db/_design/design/_view/view", headers, null); assertEquals(Status.NOT_MODIFIED, conn.getResponseCode()); // Update the database: Map<String, Object> doc4 = new HashMap<String, Object>(); doc4.put("message", "aloha"); result = (Map<String, Object>) sendBody("PUT", "/db/doc4", doc4, Status.CREATED, null); // Try a conditional GET: conn = sendRequest("GET", "/db/_design/design/_view/view", headers, null); assertEquals(Status.OK, conn.getResponseCode()); result = (Map<String, Object>) parseJSONResponse(conn); assertEquals(4, result.get("total_rows")); } public void testPostBulkDocs() { send("PUT", "/db", Status.CREATED, null); // with _id: Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("_id", "bulk_message1"); doc1.put("baz", "hello"); Map<String, Object> doc2 = new HashMap<String, Object>(); doc2.put("_id", "bulk_message2"); doc2.put("baz", "hi"); List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); list.add(doc1); list.add(doc2); Map<String, Object> bodyObj = new HashMap<String, Object>(); bodyObj.put("docs", list); List<Map<String, Object>> result = (ArrayList<Map<String, Object>>) sendBody("POST", "/db/_bulk_docs", bodyObj, Status.CREATED, null); assertEquals(2, result.size()); assertEquals(result.get(0).get("id"), doc1.get("_id")); assertNotNull(result.get(0).get("rev")); assertEquals(result.get(1).get("id"), doc2.get("_id")); assertNotNull(result.get(1).get("rev")); // without _id: doc1 = new HashMap<String, Object>(); doc1.put("baz", "hello"); doc2 = new HashMap<String, Object>(); doc2.put("baz", "hi"); list = new ArrayList<Map<String, Object>>(); list.add(doc1); list.add(doc2); bodyObj = new HashMap<String, Object>(); bodyObj.put("docs", list); result = (ArrayList<Map<String, Object>>) sendBody("POST", "/db/_bulk_docs", bodyObj, Status.CREATED, null); assertEquals(2, result.size()); assertNotNull(result.get(0).get("id")); assertNotNull(result.get(0).get("rev")); assertNotNull(result.get(1).get("id")); assertNotNull(result.get(1).get("rev")); } public void testPostBulkDocsWithConflict() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> bulk_doc1 = new HashMap<String, Object>(); bulk_doc1.put("_id", "bulk_message1"); bulk_doc1.put("baz", "hello"); Map<String, Object> bulk_doc2 = new HashMap<String, Object>(); bulk_doc2.put("_id", "bulk_message2"); bulk_doc2.put("baz", "hi"); List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); list.add(bulk_doc1); list.add(bulk_doc2); list.add(bulk_doc2); Map<String, Object> bodyObj = new HashMap<String, Object>(); bodyObj.put("docs", list); List<Map<String, Object>> bulk_result = (ArrayList<Map<String, Object>>) sendBody("POST", "/db/_bulk_docs", bodyObj, Status.CREATED, null); assertEquals(3, bulk_result.size()); assertEquals(bulk_result.get(0).get("id"), bulk_doc1.get("_id")); assertNotNull(bulk_result.get(0).get("rev")); assertEquals(bulk_result.get(1).get("id"), bulk_doc2.get("_id")); assertNotNull(bulk_result.get(1).get("rev")); assertEquals(2, bulk_result.get(2).size()); assertEquals(bulk_result.get(2).get("id"), bulk_doc2.get("_id")); assertEquals(bulk_result.get(2).get("error"), "conflict"); list = new ArrayList<Map<String, Object>>(); list.add(bulk_doc1); bodyObj = new HashMap<String, Object>(); bodyObj.put("docs", list); bulk_result = (ArrayList<Map<String, Object>>) sendBody("POST", "/db/_bulk_docs", bodyObj, Status.CREATED, null); //https://github.com/couchbase/couchbase-lite-android/issues/79 assertEquals(1, bulk_result.size()); assertEquals(2, bulk_result.get(0).size()); assertEquals(bulk_result.get(0).get("id"), bulk_doc1.get("_id")); assertEquals(bulk_result.get(0).get("error"), "conflict"); } public void testPostKeysView() throws CouchbaseLiteException { send("PUT", "/db", Status.CREATED, null); Map<String, Object> result; Database db = manager.getDatabase("db"); View view = db.getView("design/view"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("message"), null); } }, null, "1"); Map<String, Object> key_doc1 = new HashMap<String, Object>(); key_doc1.put("parentId", "12345"); result = (Map<String, Object>) sendBody("PUT", "/db/key_doc1", key_doc1, Status.CREATED, null); view = db.getView("design/view"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.get("parentId").equals("12345")) { emitter.emit(document.get("parentId"), document); } } }, null, "1"); List<Object> keys = new ArrayList<Object>(); keys.add("12345"); Map<String, Object> bodyObj = new HashMap<String, Object>(); bodyObj.put("keys", keys); URLConnection conn = sendRequest("POST", "/db/_design/design/_view/view", null, bodyObj); result = (Map<String, Object>) parseJSONResponse(conn); assertEquals(1, result.get("total_rows")); } // - (void) test_LocalDocs in Router_Tests.m public void test_RevsDiff() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> doc1r1 = (Map<String, Object>) sendBody("PUT", "/db/11111", new HashMap<String, Object>(), Status.CREATED, null); String doc1r1ID = (String) doc1r1.get("rev"); Map<String, Object> doc2r1 = (Map<String, Object>) sendBody("PUT", "/db/22222", new HashMap<String, Object>(), Status.CREATED, null); String doc2r1ID = (String) doc2r1.get("rev"); Map<String, Object> doc3r1 = (Map<String, Object>) sendBody("PUT", "/db/33333", new HashMap<String, Object>(), Status.CREATED, null); String doc3r1ID = (String) doc3r1.get("rev"); Map<String, Object> props1v2 = new HashMap<String, Object>(); props1v2.put("_rev", doc1r1ID); Map<String, Object> doc1r2 = (Map<String, Object>) sendBody("PUT", "/db/11111", props1v2, Status.CREATED, null); String doc1r2ID = (String) doc1r2.get("rev"); Map<String, Object> props2v2 = new HashMap<String, Object>(); props2v2.put("_rev", doc2r1ID); sendBody("PUT", "/db/22222", props2v2, Status.CREATED, null); Map<String, Object> props1v3 = new HashMap<String, Object>(); props1v3.put("_rev", doc1r2ID); Map<String, Object> doc1r3 = (Map<String, Object>) sendBody("PUT", "/db/11111", props1v3, Status.CREATED, null); String doc1r3ID = (String) doc1r3.get("rev"); Map<String, Object> revsDiffRequest = new HashMap<String, Object>(); revsDiffRequest.put("11111", Arrays.asList(doc1r2ID, "3-f000")); revsDiffRequest.put("22222", Arrays.asList(doc2r1ID)); revsDiffRequest.put("33333", Arrays.asList("10-badbad")); revsDiffRequest.put("99999", Arrays.asList("6-666666")); // TODO: NOTE: CBL iOS could return possible_ancestors for _revs_diff Check test_RevsDiff in Router_Tests.m //now build up the expected response Map<String, Object> doc1missingMap = new HashMap<String, Object>(); doc1missingMap.put("missing", Arrays.asList("3-f000")); Map<String, Object> doc3missingMap = new HashMap<String, Object>(); doc3missingMap.put("missing", Arrays.asList("10-badbad")); Map<String, Object> doc9missingMap = new HashMap<String, Object>(); doc9missingMap.put("missing", Arrays.asList("6-666666")); Map<String, Object> revsDiffResponse = new HashMap<String, Object>(); revsDiffResponse.put("11111", doc1missingMap); revsDiffResponse.put("33333", doc3missingMap); revsDiffResponse.put("99999", doc9missingMap); sendBody("POST", "/db/_revs_diff", revsDiffRequest, Status.OK, revsDiffResponse); // Compact the database -- this will null out the JSON of doc1r1 & doc1r2, // and they won't be returned as possible ancestors anymore. send("POST", "/db/_compact", Status.ACCEPTED, null); revsDiffRequest = new HashMap<String, Object>(); revsDiffRequest.put("11111", Arrays.asList(doc1r2ID, "4-f000")); revsDiffRequest.put("22222", Arrays.asList(doc2r1ID)); revsDiffRequest.put("33333", Arrays.asList("10-badbad")); revsDiffRequest.put("99999", Arrays.asList("6-666666")); doc1missingMap = new HashMap<String, Object>(); doc1missingMap.put("missing", Arrays.asList("4-f000")); doc3missingMap = new HashMap<String, Object>(); doc3missingMap.put("missing", Arrays.asList("10-badbad")); doc9missingMap = new HashMap<String, Object>(); doc9missingMap.put("missing", Arrays.asList("6-666666")); revsDiffResponse = new HashMap<String, Object>(); revsDiffResponse.put("11111", doc1missingMap); revsDiffResponse.put("33333", doc3missingMap); revsDiffResponse.put("99999", doc9missingMap); sendBody("POST", "/db/_revs_diff", revsDiffRequest, Status.OK, revsDiffResponse); // get document without revs_info=true Map<String, Object> docResponse = new HashMap<String, Object>(); docResponse.put("_id", "11111"); docResponse.put("_rev", doc1r3ID); send("GET", "/db/11111", Status.OK, docResponse); // Check the revision history using _revs_info: Map<String, Object> doc1Rev3Res = new HashMap<String, Object>(); doc1Rev3Res.put("rev", doc1r3ID); doc1Rev3Res.put("status", "available"); Map<String, Object> doc1Rev2Res = new HashMap<String, Object>(); doc1Rev2Res.put("rev", doc1r2ID); doc1Rev2Res.put("status", "missing"); Map<String, Object> doc1Rev1Res = new HashMap<String, Object>(); doc1Rev1Res.put("rev", doc1r1ID); doc1Rev1Res.put("status", "missing"); docResponse = new HashMap<String, Object>(); docResponse.put("_id", "11111"); docResponse.put("_rev", doc1r3ID); docResponse.put("_revs_info", Arrays.asList(doc1Rev3Res, doc1Rev2Res, doc1Rev1Res)); send("GET", "/db/11111?revs_info=true", Status.OK, docResponse); // Check the revision history using _revs: Map<String, Object> revsResp = new HashMap<String, Object>(); revsResp.put("start", 3); revsResp.put("ids", Arrays.asList( doc1r3ID.substring(2), doc1r2ID.substring(2), doc1r1ID.substring(2))); docResponse = new HashMap<String, Object>(); docResponse.put("_id", "11111"); docResponse.put("_rev", doc1r3ID); docResponse.put("_revisions", revsResp); send("GET", "/db/11111?revs=true", Status.OK, docResponse); } public void testFacebookToken() throws Exception { send("PUT", "/db", Status.CREATED, null); Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("email", "foo@bar.com"); doc1.put("remote_url", getReplicationURL().toExternalForm()); doc1.put("access_token", "fake_access_token"); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_facebook_token", doc1, Status.OK, null); Log.v(TAG, "result %s", result); } public void testPersonaAssertion() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> doc1 = new HashMap<String, Object>(); String sampleAssertion = "eyJhbGciOiJSUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IkRTIiwieSI6ImNhNWJiYTYzZmI4MDQ2OGE0MjFjZjgxYTIzN2VlMDcwYTJlOTM4NTY0ODhiYTYzNTM0ZTU4NzJjZjllMGUwMDk0ZWQ2NDBlOGNhYmEwMjNkYjc5ODU3YjkxMzBlZGNmZGZiNmJiNTUwMWNjNTk3MTI1Y2NiMWQ1ZWQzOTVjZTMyNThlYjEwN2FjZTM1ODRiOWIwN2I4MWU5MDQ4NzhhYzBhMjFlOWZkYmRjYzNhNzNjOTg3MDAwYjk4YWUwMmZmMDQ4ODFiZDNiOTBmNzllYzVlNDU1YzliZjM3NzFkYjEzMTcxYjNkMTA2ZjM1ZDQyZmZmZjQ2ZWZiZDcwNjgyNWQiLCJwIjoiZmY2MDA0ODNkYjZhYmZjNWI0NWVhYjc4NTk0YjM1MzNkNTUwZDlmMWJmMmE5OTJhN2E4ZGFhNmRjMzRmODA0NWFkNGU2ZTBjNDI5ZDMzNGVlZWFhZWZkN2UyM2Q0ODEwYmUwMGU0Y2MxNDkyY2JhMzI1YmE4MWZmMmQ1YTViMzA1YThkMTdlYjNiZjRhMDZhMzQ5ZDM5MmUwMGQzMjk3NDRhNTE3OTM4MDM0NGU4MmExOGM0NzkzMzQzOGY4OTFlMjJhZWVmODEyZDY5YzhmNzVlMzI2Y2I3MGVhMDAwYzNmNzc2ZGZkYmQ2MDQ2MzhjMmVmNzE3ZmMyNmQwMmUxNyIsInEiOiJlMjFlMDRmOTExZDFlZDc5OTEwMDhlY2FhYjNiZjc3NTk4NDMwOWMzIiwiZyI6ImM1MmE0YTBmZjNiN2U2MWZkZjE4NjdjZTg0MTM4MzY5YTYxNTRmNGFmYTkyOTY2ZTNjODI3ZTI1Y2ZhNmNmNTA4YjkwZTVkZTQxOWUxMzM3ZTA3YTJlOWUyYTNjZDVkZWE3MDRkMTc1ZjhlYmY2YWYzOTdkNjllMTEwYjk2YWZiMTdjN2EwMzI1OTMyOWU0ODI5YjBkMDNiYmM3ODk2YjE1YjRhZGU1M2UxMzA4NThjYzM0ZDk2MjY5YWE4OTA0MWY0MDkxMzZjNzI0MmEzODg5NWM5ZDViY2NhZDRmMzg5YWYxZDdhNGJkMTM5OGJkMDcyZGZmYTg5NjIzMzM5N2EifSwicHJpbmNpcGFsIjp7ImVtYWlsIjoiamVuc0Btb29zZXlhcmQuY29tIn0sImlhdCI6MTM1ODI5NjIzNzU3NywiZXhwIjoxMzU4MzgyNjM3NTc3LCJpc3MiOiJsb2dpbi5wZXJzb25hLm9yZyJ9.RnDK118nqL2wzpLCVRzw1MI4IThgeWpul9jPl6ypyyxRMMTurlJbjFfs-BXoPaOem878G8-4D2eGWS6wd307k7xlPysevYPogfFWxK_eDHwkTq3Ts91qEDqrdV_JtgULC8c1LvX65E0TwW_GL_TM94g3CvqoQnGVxxoaMVye4ggvR7eOZjimWMzUuu4Lo9Z-VBHBj7XM0UMBie57CpGwH4_Wkv0V_LHZRRHKdnl9ISp_aGwfBObTcHG9v0P3BW9vRrCjihIn0SqOJQ9obl52rMf84GD4Lcy9NIktzfyka70xR9Sh7ALotW7rWywsTzMTu3t8AzMz2MJgGjvQmx49QA~eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzNTgyOTY0Mzg0OTUsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDk4NC8ifQ.4FV2TrUQffDya0MOxOQlzJQbDNvCPF2sfTIJN7KOLvvlSFPknuIo5g"; doc1.put("assertion", sampleAssertion); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_persona_assertion", doc1, Status.OK, null); Log.v(TAG, "result %s", result); String email = (String) result.get("email"); assertEquals(email, "jens@mooseyard.com"); } public void testPushReplicate() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getMockWebServer(dispatcher); dispatcher.setServerType(MockDispatcher.ServerType.SYNC_GW); try { // fake checkpoint response 404 MockCheckpointGet mockCheckpointGet = new MockCheckpointGet(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, mockCheckpointGet); server.start(); Set<String> sessionIds = new HashSet<String>(); Map<String, Object> replicator1 = getPushReplicationProperties(server.url("/db").url()); Map<String, Object> result1 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator1, Status.OK, null); assertTrue(result1.containsKey("ok")); assertTrue(((Boolean)result1.get("ok")).booleanValue()); String sessionId1 = (String)result1.get("session_id"); assertNotNull(sessionId1); sessionIds.add(sessionId1); Map<String, Object> replicator2 = getPushReplicationProperties(server.url("/db").url()); replicator2.put("continuous", true); Map<String, Object> result2 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator2, Status.OK, null); assertTrue(result2.containsKey("ok")); assertTrue(((Boolean)result2.get("ok")).booleanValue()); String sessionId2 = (String)result2.get("session_id"); assertNotNull(sessionId2); sessionIds.add(sessionId2); Map<String, Object> replicator3 = getPushReplicationProperties(server.url("/db").url()); replicator3.put("continuous", true); replicator3.put("doc_ids", Arrays.asList(((String[]) new String[]{"doc1", "doc2"}))); Map<String, Object> result3 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator3, Status.OK, null); assertTrue(result3.containsKey("ok")); assertTrue(((Boolean)result3.get("ok")).booleanValue()); String sessionId3 = (String)result3.get("session_id"); assertNotNull(sessionId3); sessionIds.add(sessionId3); Map<String, Object> replicator4 = getPushReplicationProperties(server.url("/db").url()); replicator4.put("continuous", true); replicator4.put("filter", "myfilter"); Map<String, Object> result4 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator4, Status.OK, null); assertTrue(result4.containsKey("ok")); assertTrue(((Boolean)result4.get("ok")).booleanValue()); String sessionId4 = (String)result4.get("session_id"); assertNotNull(sessionId4); sessionIds.add(sessionId4); assertEquals(4, sessionIds.size()); replicator2.put("cancel", true); replicator3.put("cancel", true); replicator4.put("cancel", true); sendBody("POST", "/_replicate", replicator2, Status.OK, null); sendBody("POST", "/_replicate", replicator3, Status.OK, null); sendBody("POST", "/_replicate", replicator4, Status.OK, null); boolean success = waitForReplicationToFinish(); assertTrue(success); } finally { server.shutdown(); } } private boolean waitForReplicationToFinish() throws InterruptedException { int maxTimeToWaitMs = 30 * 1000; int timeWaited = 0; boolean success = true; ArrayList<Object> activeTasks = (ArrayList<Object>) send("GET", "/_active_tasks", Status.OK, null); while (activeTasks.size() > 0 && timeWaited < maxTimeToWaitMs) { int timeToWait = 200; Thread.sleep(timeToWait); activeTasks = (ArrayList<Object>) send("GET", "/_active_tasks", Status.OK, null); timeWaited += timeToWait; } if (timeWaited >= maxTimeToWaitMs) { success = false; } return success; } public void testPullReplicate() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs int numMockDocsToServe = 0; MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getPreloadedPullTargetMockCouchDB(dispatcher, numMockDocsToServe, 1); dispatcher.setServerType(MockDispatcher.ServerType.COUCHDB); server.setDispatcher(dispatcher); try { server.start(); Set<String> sessionIds = new HashSet<String>(); Map<String, Object> replicator1 = getPullReplicationProperties(server.url("/db").url()); Map<String, Object> result1 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator1, Status.OK, null); assertTrue(result1.containsKey("ok")); assertTrue(((Boolean)result1.get("ok")).booleanValue()); String sessionId1 = (String)result1.get("session_id"); assertNotNull(sessionId1); sessionIds.add(sessionId1); Map<String, Object> replicator2 = getPullReplicationProperties(server.url("/db").url()); replicator2.put("continuous", true); Map<String, Object> result2 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator2, Status.OK, null); assertTrue(result2.containsKey("ok")); assertTrue(((Boolean)result2.get("ok")).booleanValue()); String sessionId2 = (String)result2.get("session_id"); assertNotNull(sessionId1); sessionIds.add(sessionId2); Map<String, Object> replicator3 = getPullReplicationProperties(server.url("/db").url()); replicator3.put("continuous", true); replicator3.put("doc_ids", Arrays.asList(((String[]) new String[]{"doc1", "doc2"}))); Map<String, Object> result3 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator3, Status.OK, null); assertTrue(result3.containsKey("ok")); assertTrue(((Boolean)result3.get("ok")).booleanValue()); String sessionId3 = (String)result3.get("session_id"); assertNotNull(sessionId3); sessionIds.add(sessionId3); Map<String, Object> replicator4 = getPullReplicationProperties(server.url("/db").url()); replicator4.put("continuous", true); replicator4.put("filter", "myfilter"); Map<String, Object> result4 = (Map<String, Object>) sendBody("POST", "/_replicate", replicator4, Status.OK, null); assertTrue(result4.containsKey("ok")); assertTrue(((Boolean)result4.get("ok")).booleanValue()); String sessionId4 = (String)result4.get("session_id"); assertNotNull(sessionId4); sessionIds.add(sessionId4); assertEquals(4, sessionIds.size()); replicator2.put("cancel", true); replicator3.put("cancel", true); replicator4.put("cancel", true); sendBody("POST", "/_replicate", replicator2, Status.OK, null); sendBody("POST", "/_replicate", replicator3, Status.OK, null); sendBody("POST", "/_replicate", replicator4, Status.OK, null); // wait for replication to finish boolean success = waitForReplicationToFinish(); assertTrue(success); } finally { // cleanup server.shutdown(); } } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/106 */ public void testResolveConflict() throws Exception { Map<String, Object> result; // Create a conflict on purpose Document doc = database.createDocument(); SavedRevision rev1 = doc.createRevision().save(); SavedRevision rev2a = createRevisionWithRandomProps(rev1, false); SavedRevision rev2b = createRevisionWithRandomProps(rev1, true); SavedRevision winningRev = null; SavedRevision losingRev = null; if (doc.getCurrentRevisionId().equals(rev2a.getId())) { winningRev = rev2a; losingRev = rev2b; } else { winningRev = rev2b; losingRev = rev2a; } assertEquals(2, doc.getConflictingRevisions().size()); assertEquals(2, doc.getLeafRevisions().size()); result = (Map<String, Object>) send("GET", String.format(Locale.ENGLISH, "/%s/%s?conflicts=true", DEFAULT_TEST_DB, doc.getId()), Status.OK, null); List<String> conflicts = (List) result.get("_conflicts"); assertEquals(1, conflicts.size()); String conflictingRevId = conflicts.get(0); assertEquals(losingRev.getId(), conflictingRevId); assertNotNull(database.getDocument(doc.getId())); result = (Map<String, Object>) send("DELETE", String.format(Locale.ENGLISH, "/%s/%s?rev=%s", DEFAULT_TEST_DB, doc.getId(), conflictingRevId), Status.OK, null); result = (Map<String, Object>) send("GET", String.format(Locale.ENGLISH, "/%s/%s?conflicts=true", DEFAULT_TEST_DB, doc.getId()), Status.OK, null); conflicts = (List) result.get("_conflicts"); assertEquals(0, conflicts.size()); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/293 */ public void testTotalRowsAttributeOnViewQuery() throws CouchbaseLiteException { send("PUT", "/db", Status.CREATED, null); // PUT: Map<String, Object> result; Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); Map<String, Object> doc3 = new HashMap<String, Object>(); doc3.put("message", "bonjour"); result = (Map<String, Object>) sendBody("PUT", "/db/doc3", doc3, Status.CREATED, null); String revID3 = (String) result.get("rev"); Map<String, Object> doc2 = new HashMap<String, Object>(); doc2.put("message", "guten tag"); result = (Map<String, Object>) sendBody("PUT", "/db/doc2", doc2, Status.CREATED, null); String revID2 = (String) result.get("rev"); Database db = manager.getDatabase("db"); View view = db.getView("design/view"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("message"), null); } }, null, "1"); // Build up our expected result Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("id", "doc1"); row1.put("key", "hello"); row1.put("doc", null); row1.put("value", null); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("id", "doc2"); row2.put("key", "guten tag"); row2.put("doc", null); row2.put("value", null); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("id", "doc3"); row3.put("key", "bonjour"); row3.put("doc", null); row3.put("value", null); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); expectedRows.add(row3); expectedRows.add(row2); //expectedRows.add(row1); Map<String, Object> expectedResult = new HashMap<String, Object>(); expectedResult.put("offset", 0); expectedResult.put("total_rows", 3); expectedResult.put("rows", expectedRows); // Query the view and check the result: send("GET", "/db/_design/design/_view/view?limit=2", Status.OK, expectedResult); // Check the ETag: URLConnection conn = sendRequest("GET", "/db/_design/design/_view/view", null, null); String etag = conn.getHeaderField("Etag"); assertEquals(String.format(Locale.ENGLISH, "\"%d\"", view.getLastSequenceIndexed()), etag); // Try a conditional GET: Map<String, String> headers = new HashMap<String, String>(); headers.put("If-None-Match", etag); conn = sendRequest("GET", "/db/_design/design/_view/view", headers, null); assertEquals(Status.NOT_MODIFIED, conn.getResponseCode()); // Update the database: Map<String, Object> doc4 = new HashMap<String, Object>(); doc4.put("message", "aloha"); result = (Map<String, Object>) sendBody("PUT", "/db/doc4", doc4, Status.CREATED, null); // Try a conditional GET: conn = sendRequest("GET", "/db/_design/design/_view/view?limit=2", headers, null); assertEquals(Status.OK, conn.getResponseCode()); result = (Map<String, Object>) parseJSONResponse(conn); assertEquals(2, ((List) result.get("rows")).size()); assertEquals(4, result.get("total_rows")); } public void testSession() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> session = new HashMap<String, Object>(); Map<String, Object> userCtx = new HashMap<String, Object>(); List<String> roles = new ArrayList<String>(); roles.add("_admin"); session.put("ok", true); userCtx.put("name", null); userCtx.put("roles", roles); session.put("userCtx", userCtx); send("GET", "/_session", Status.OK, session); send("GET", "/db/_session", Status.OK, session); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/291 */ public void testCallReplicateTwice() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs int numMockDocsToServe = 0; MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getPreloadedPullTargetMockCouchDB(dispatcher, numMockDocsToServe, 1); dispatcher.setServerType(MockDispatcher.ServerType.COUCHDB); server.setDispatcher(dispatcher); try { server.start(); // kick off 1st replication via REST api Map<String, Object> replicateJsonMap = getPullReplicationProperties(server.url("/db").url()); Log.i(TAG, "map: " + replicateJsonMap); Log.i(TAG, "Call 1st /_replicate"); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result.containsKey("ok")); assertTrue(((Boolean)result.get("ok")).booleanValue()); Log.i(TAG, "result: " + result); assertNotNull(result.get("session_id")); String sessionId1 = (String) result.get("session_id"); // NOTE: one short replication should be blocked. sendBody() waits till response is ready. // https://github.com/couchbase/couchbase-lite-android/issues/204 // 0 changes MockChangesFeed mockChangesFeedEmpty = new MockChangesFeed(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHANGES, mockChangesFeedEmpty.generateMockResponse()); // kick off 2nd replication via REST api Log.i(TAG, "Call 2nd /_replicate"); Map<String, Object> result2 = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result2.containsKey("ok")); assertTrue(((Boolean)result2.get("ok")).booleanValue()); Log.i(TAG, "result2: " + result2); assertNotNull(result2.get("session_id")); String sessionId2 = (String) result2.get("session_id"); // wait for replication to finish boolean success = waitForReplicationToFinish(); assertTrue(success); // 0 changes dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHANGES, mockChangesFeedEmpty.generateMockResponse()); // kick off 3rd replication via REST api Log.i(TAG, "Call 3rd /_replicate"); Map<String, Object> result3 = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result3.containsKey("ok")); assertTrue(((Boolean)result3.get("ok")).booleanValue()); Log.i(TAG, "result3: " + result3); assertNotNull(result3.get("session_id")); String sessionId3 = (String) result3.get("session_id"); // wait for replication to finish boolean success3 = waitForReplicationToFinish(); assertTrue(success3); assertFalse(sessionId1.equals(sessionId2)); assertFalse(sessionId1.equals(sessionId3)); assertFalse(sessionId2.equals(sessionId3)); } finally { // cleanup server.shutdown(); } } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/291 */ public void testCallContinuousReplicateTwice() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs int numMockDocsToServe = 0; MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getPreloadedPullTargetMockCouchDB(dispatcher, numMockDocsToServe, 1); dispatcher.setServerType(MockDispatcher.ServerType.COUCHDB); server.setDispatcher(dispatcher); try { server.start(); // kick off 1st replication via REST api Map<String, Object> replicateJsonMap = getPullReplicationProperties(server.url("/db").url()); replicateJsonMap.put("continuous", true); Log.i(TAG, "map: " + replicateJsonMap); Log.i(TAG, "Call 1st /_replicate"); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result.containsKey("ok")); assertTrue(((Boolean)result.get("ok")).booleanValue()); Log.i(TAG, "result: " + result); assertNotNull(result.get("session_id")); String sessionId1 = (String) result.get("session_id"); // no wait, immediately call new _replicate REST API // kick off 2nd replication via REST api => Should be Log.i(TAG, "Call 2nd /_replicate"); Map<String, Object> result2 = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result2.containsKey("ok")); assertTrue(((Boolean)result2.get("ok")).booleanValue()); Log.i(TAG, "result2: " + result2); assertNotNull(result2.get("session_id")); String sessionId2 = (String) result2.get("session_id"); // WAIT replicator becomes IDLE List<CountDownLatch> replicationIdleSignals = new ArrayList<CountDownLatch>(); List<Replication> repls = database.getActiveReplications(); for (Replication repl : repls) { if (repl.getStatus() == Replication.ReplicationStatus.REPLICATION_ACTIVE) { final CountDownLatch replicationIdleSignal = new CountDownLatch(1); ReplicationIdleObserver idleObserver = new ReplicationIdleObserver(replicationIdleSignal); repl.addChangeListener(idleObserver); replicationIdleSignals.add(replicationIdleSignal); } } for (CountDownLatch signal : replicationIdleSignals) { assertTrue(signal.await(30, TimeUnit.SECONDS)); } // kick off 3rd replication via REST api => Should be Log.i(TAG, "Call 3rd /_replicate"); Map<String, Object> result3 = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result3.containsKey("ok")); assertTrue(((Boolean)result3.get("ok")).booleanValue()); Log.i(TAG, "result3: " + result3); assertNotNull(result3.get("session_id")); String sessionId3 = (String) result3.get("session_id"); // WAIT replicator becomes IDLE replicationIdleSignals = new ArrayList<CountDownLatch>(); repls = database.getActiveReplications(); for (Replication repl : repls) { if (repl.getStatus() == Replication.ReplicationStatus.REPLICATION_ACTIVE) { final CountDownLatch replicationIdleSignal = new CountDownLatch(1); ReplicationIdleObserver idleObserver = new ReplicationIdleObserver(replicationIdleSignal); repl.addChangeListener(idleObserver); replicationIdleSignals.add(replicationIdleSignal); } } for (CountDownLatch signal : replicationIdleSignals) { assertTrue(signal.await(30, TimeUnit.SECONDS)); } // Cancel Replicator replicateJsonMap.put("cancel", true); Log.i(TAG, "map: " + replicateJsonMap); Map<String, Object> result4 = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result4.containsKey("ok")); assertTrue(((Boolean)result4.get("ok")).booleanValue()); Log.i(TAG, "result4: " + result4); // wait for replication to finish boolean success = waitForReplicationToFinish(); assertTrue(success); assertTrue(sessionId1.equals(sessionId2)); assertTrue(sessionId1.equals(sessionId3)); } finally { // cleanup server.shutdown(); } } public void testPullReplicateOneShot() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs int numMockDocsToServe = 0; MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getPreloadedPullTargetMockCouchDB(dispatcher, numMockDocsToServe, 1); dispatcher.setServerType(MockDispatcher.ServerType.COUCHDB); server.setDispatcher(dispatcher); try { server.start(); // kick off replication via REST api Map<String, Object> replicateJsonMap = getPullReplicationProperties(server.url("/db").url()); Log.i(TAG, "map: " + replicateJsonMap); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result.containsKey("ok")); assertTrue(((Boolean)result.get("ok")).booleanValue()); Log.i(TAG, "result: " + result); assertNotNull(result.get("session_id")); ArrayList<Object> activeTasks = (ArrayList<Object>) send("GET", "/_active_tasks", Status.OK, null); Log.v(TAG, "activeTasks.size(): " + activeTasks.size()); for (Object obj : activeTasks) { Map<String, Object> resp = (Map<String, Object>) obj; assertEquals("Stopped", resp.get("status")); } } finally { // cleanup server.shutdown(); } } public void testChangesIncludeDocs() throws CouchbaseLiteException { // Create a conflict on purpose Document doc = database.createDocument(); SavedRevision rev1 = doc.createRevision().save(); SavedRevision rev2a = createRevisionWithRandomProps(rev1, false); SavedRevision rev2b = createRevisionWithRandomProps(rev1, true); SavedRevision winningRev = null; SavedRevision losingRev = null; if (doc.getCurrentRevisionId().equals(rev2a.getId())) { winningRev = rev2a; losingRev = rev2b; } else { winningRev = rev2b; losingRev = rev2a; } assertEquals(2, doc.getConflictingRevisions().size()); assertEquals(2, doc.getLeafRevisions().size()); // /{db}/_changes with default parameter -> in changes, one revisions [winning rev] String url = String.format(Locale.ENGLISH, "/%s/_changes", DEFAULT_TEST_DB); Map<String, Object> res = (Map<String, Object>) send("GET", url, Status.OK, null); Log.i(TAG, "[%s] %s", url, res); String[] revIDs = obtainChangesRevIDs(res); assertEquals(1, revIDs.length); assertEquals(winningRev.getId(), revIDs[0]); // /{db}/_changes with style=all_docs parameter -> in changes, two revisions [winning rev and losing rev] url = String.format(Locale.ENGLISH, "/%s/_changes?style=all_docs", DEFAULT_TEST_DB); res = (Map<String, Object>) send("GET", url, Status.OK, null); Log.i(TAG, "[%s] %s", url, res); revIDs = obtainChangesRevIDs(res); assertEquals(2, revIDs.length); assertEquals(winningRev.getId(), revIDs[0]); assertEquals(losingRev.getId(), revIDs[1]); // /{db}/_changes with include_docs=true parameter -> in changes, one revisions [winning rev] and doc is only winning rev url = String.format(Locale.ENGLISH, "/%s/_changes?include_docs=true", DEFAULT_TEST_DB); res = (Map<String, Object>) send("GET", url, Status.OK, null); Log.i(TAG, "[%s] %s", url, res); revIDs = obtainChangesRevIDs(res); assertEquals(1, revIDs.length); assertEquals(winningRev.getId(), revIDs[0]); // /{db}/_changes with include_docs=true & style=all_docs parameters -> in changes, one revisions [winning rev] and doc is only winning rev url = String.format(Locale.ENGLISH, "/%s/_changes?include_docs=true&style=all_docs", DEFAULT_TEST_DB); res = (Map<String, Object>) send("GET", url, Status.OK, null); Log.i(TAG, "[%s] %s", url, res); revIDs = obtainChangesRevIDs(res); assertEquals(2, revIDs.length); assertEquals(winningRev.getId(), revIDs[0]); assertEquals(losingRev.getId(), revIDs[1]); // NOTE: To test feed=longpoll, use AndroidLiteServ } private static String[] obtainChangesRevIDs(Map<String, Object> res) { List<String> revIDs = new ArrayList<String>(); List<Map<String, Object>> results = (List<Map<String, Object>>) res.get("results"); Map<String, Object> result = results.get(0); List<Map<String, Object>> changes = (List<Map<String, Object>>) result.get("changes"); for (Map<String, Object> change : changes) { revIDs.add((String) change.get("rev")); } return revIDs.toArray(new String[revIDs.size()]); } // https://github.com/couchbase/couchbase-lite-android/issues/660 // https://github.com/couchbase/couchbase-lite-java-core/issues/191 public void test_do_DELETE_Attachment() throws IOException { String inlineTextString = "Inline text string created by cblite functional test"; // PUT /{db} send("PUT", "/db", Status.CREATED, null); // PUT /{db}/{doc} Map<String, Object> attachment = new HashMap<String, Object>(); attachment.put("content_type", "text/plain"); attachment.put("data", "SW5saW5lIHRleHQgc3RyaW5nIGNyZWF0ZWQgYnkgY2JsaXRlIGZ1bmN0aW9uYWwgdGVzdA=="); Map<String, Object> attachments = new HashMap<String, Object>(); attachments.put("inline.txt", attachment); Map<String, Object> docWithAttachment = new HashMap<String, Object>(); docWithAttachment.put("_id", "docWithAttachment"); docWithAttachment.put("text", inlineTextString); docWithAttachment.put("_attachments", attachments); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/docWithAttachment", docWithAttachment, Status.CREATED, null); // GET /{db}/{doc} result = (Map<String, Object>) send("GET", "/db/docWithAttachment", Status.OK, null); Map<String, Object> attachmentsResult = (Map<String, Object>) result.get("_attachments"); Map<String, Object> attachmentResult = (Map<String, Object>) attachmentsResult.get("inline.txt"); // there should be either a content_type or content-type field. //https://github.com/couchbase/couchbase-lite-android-core/issues/12 //content_type becomes null for attachments in responses, should be as set in Content-Type String contentTypeField = (String) attachmentResult.get("content_type"); assertTrue(attachmentResult.containsKey("content_type")); assertNotNull(contentTypeField); String revID = (String) result.get("_rev"); assertNotNull(revID); // GET /{db}/{doc}/{attachment} // no Accept URLConnection conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", null, null); String contentType = conn.getHeaderField("Content-Type"); assertNotNull(contentType); assertTrue(contentType.contains("text/plain")); StringWriter writer = new StringWriter(); InputStream is = conn.getInputStream(); IOUtils.copy(is, writer, "UTF-8"); is.close(); String responseString = writer.toString(); assertTrue(responseString.contains(inlineTextString)); writer.close(); // DELETE /{db}/{doc}/{attachment} - without rev query parameter. send("DELETE", "/db/docWithAttachment/inline.txt", Status.BAD_REQUEST, null); // DELETE /{db}/{doc}/{attachment} result = (Map<String, Object>) send("DELETE", "/db/docWithAttachment/inline.txt?rev=" + revID, Status.OK, null); assertNotNull(result); assertEquals("docWithAttachment", (String) result.get("id")); assertNotNull(result.get("rev")); assertEquals(Boolean.TRUE, (Boolean) result.get("ok")); revID = (String) result.get("rev"); // GET /{db}/{doc} result = (Map<String, Object>) send("GET", "/db/docWithAttachment", Status.OK, null); assertEquals("docWithAttachment", (String) result.get("_id")); assertNotNull(result.get("_rev")); assertEquals(revID, (String) result.get("_rev")); assertEquals(inlineTextString, (String) result.get("text")); assertNull(result.get("_attachments")); // GET /{db}/{doc}/{attachment} // no Accept conn = sendRequest("GET", "/db/docWithAttachment/inline.txt", null, null); assertEquals(404, conn.getResponseCode()); contentType = conn.getHeaderField("Content-Type"); writer = new StringWriter(); is = conn.getInputStream(); IOUtils.copy(is, writer, "UTF-8"); is.close(); responseString = writer.toString(); assertTrue(responseString.contains("404")); assertTrue(responseString.contains("not_found")); writer.close(); } // https://github.com/couchbase/couchbase-lite-android/issues/476 public void testReplicateWithDocIDs() throws Exception { // create mock sync gateway that will serve as a pull target and return random docs int numMockDocsToServe = 0; MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getPreloadedPullTargetMockCouchDB(dispatcher, numMockDocsToServe, 1); dispatcher.setServerType(MockDispatcher.ServerType.COUCHDB); server.setDispatcher(dispatcher); try { server.start(); // kick off replication via REST api Map<String, Object> replicateJsonMap = getPullReplicationProperties(server.url("/db").url()); List<String> docIDs = new ArrayList(); docIDs.add("doc0"); replicateJsonMap.put("doc_ids", docIDs); Log.i(TAG, "map: " + replicateJsonMap); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, Status.OK, null); assertTrue(result.containsKey("ok")); assertTrue(((Boolean)result.get("ok")).booleanValue()); Log.i(TAG, "result: " + result); assertNotNull(result.get("session_id")); // Check if /_changes calls includes doc_ids in body. RecordedRequest getChangesFeedRequest = dispatcher.takeRequest(MockHelper.PATH_REGEX_CHANGES); assertTrue(getChangesFeedRequest.getMethod().equals("POST")); String body = getChangesFeedRequest.getUtf8Body(); Map<String, Object> jsonMap = Manager.getObjectMapper().readValue(body, Map.class); assertTrue(jsonMap.containsKey("filter")); String filter = (String) jsonMap.get("filter"); assertEquals("_doc_ids", filter); List<String> docids = (List<String>) jsonMap.get("doc_ids"); assertNotNull(docids); assertEquals(1, docids.size()); assertTrue(docIDs.contains("doc0")); } finally { server.shutdown(); } } // Querying a design document view returns deleted documents too REST API // https://github.com/couchbase/couchbase-lite-java-core/issues/1264 // https://forums.couchbase.com/t/querying-a-design-document-view-returns-deleted-documents-too-rest-api/8643 public void testViewWithDeletedDoc() throws CouchbaseLiteException { send("PUT", "/db", Status.CREATED, null); // PUT: Map<String, Object> result; Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("message", "hello"); result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); Map<String, Object> doc3 = new HashMap<String, Object>(); doc3.put("message", "bonjour"); result = (Map<String, Object>) sendBody("PUT", "/db/doc3", doc3, Status.CREATED, null); String revID3 = (String) result.get("rev"); Map<String, Object> doc2 = new HashMap<String, Object>(); doc2.put("message", "guten tag"); result = (Map<String, Object>) sendBody("PUT", "/db/doc2", doc2, Status.CREATED, null); String revID2 = (String) result.get("rev"); Database db = manager.getDatabase("db"); View view = db.getView("design/view"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("message"), null); } }, null, "1"); // Build up our expected result Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("id", "doc1"); row1.put("key", "hello"); row1.put("doc", null); row1.put("value", null); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("id", "doc2"); row2.put("key", "guten tag"); row2.put("doc", null); row2.put("value", null); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("id", "doc3"); row3.put("key", "bonjour"); row3.put("doc", null); row3.put("value", null); Map<String, Object> expectedResult = new HashMap<String, Object>(); expectedResult.put("offset", 0); expectedResult.put("total_rows", 3); expectedResult.put("rows", Arrays.asList(row3, row2, row1)); // Query the view and check the result: send("GET", "/db/_design/design/_view/view", Status.OK, expectedResult); // DELETE: result = (Map<String, Object>) send("DELETE", String.format(Locale.ENGLISH, "/db/doc1?rev=%s", revID), Status.OK, null); revID = (String) result.get("rev"); assertTrue(revID.startsWith("2-")); assertEquals("doc1", (String) result.get("id")); assertTrue((Boolean)result.get("ok")); // Query the view and check the result: // doc1 should not be in the results expectedResult = new HashMap<String, Object>(); expectedResult.put("offset", 0); expectedResult.put("total_rows", 2); expectedResult.put("rows", Arrays.asList(row3, row2)); send("GET", "/db/_design/design/_view/view", Status.OK, expectedResult); } public void testLongpollChangesTimeout() { send("PUT", "/db", Status.CREATED, null); Map<String, Object> result = new HashMap<String, Object>(); result.put("results", new ArrayList<Object>()); result.put("last_seq", 0); send("GET", "/db/_changes?feed=longpoll&timeout=2000&since=0", Status.OK, result); new HashMap<String, Object>(); result.put("results", new ArrayList<Object>()); result.put("last_seq", 5); send("GET", "/db/_changes?feed=longpoll&timeout=2000&since=5", Status.OK, result); } public void testContinuousChangesTimeout() throws Exception { send("PUT", "/db", Status.CREATED, null); URLConnection conn; String [] expected = new String[] { "{\"last_seq\":0}" }; conn = sendRequest("GET", "/db/_changes?feed=continuous&timeout=2000&since=0", null, null); String[] changes = IOUtils.toString(conn.getResponseInputStream()).split("\\n"); assertTrue(Arrays.equals(changes, expected)); expected = new String[] { "{\"last_seq\":5}" }; conn = sendRequest("GET", "/db/_changes?feed=continuous&timeout=2000&since=5", null, null); changes = IOUtils.toString(conn.getResponseInputStream()).split("\\n"); assertTrue(compareContinuousFeed(expected, changes)); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("foo", "bar"); Map<String, Object> doc1 = (Map<String, Object>) sendBody("PUT", "/db/doc1", properties, Status.CREATED, null); Map<String, Object> doc2 = (Map<String, Object>) sendBody("PUT", "/db/doc2", properties, Status.CREATED, null); expected = new String[] { "{\"seq\":1,\"id\":\"doc1\",\"changes\":[{\"rev\":\"" + doc1.get("rev") + "\"}]}", "{\"seq\":2,\"id\":\"doc2\",\"changes\":[{\"rev\":\"" + doc2.get("rev") + "\"}]}", "{\"last_seq\":2}" }; conn = sendRequest("GET", "/db/_changes?feed=continuous&timeout=2000&since=0", null, null); changes = IOUtils.toString(conn.getResponseInputStream()).split("\\n"); assertTrue(compareContinuousFeed(expected, changes)); expected = new String[] { "{\"seq\":2,\"id\":\"doc2\",\"changes\":[{\"rev\":\"" + doc2.get("rev") + "\"}]}", "{\"last_seq\":2}" }; conn = sendRequest("GET", "/db/_changes?feed=continuous&timeout=2000&since=1", null, null); changes = IOUtils.toString(conn.getResponseInputStream()).split("\\n"); assertTrue(compareContinuousFeed(expected, changes)); expected = new String[] { "{\"last_seq\":5}" }; conn = sendRequest("GET", "/db/_changes?feed=continuous&timeout=2000&since=5", null, null); changes = IOUtils.toString(conn.getResponseInputStream()).split("\\n"); assertTrue(compareContinuousFeed(expected, changes)); } private boolean compareContinuousFeed(String[] expectedFeed, String[] feed) throws IOException { if (expectedFeed == null || feed == null) return false; if (expectedFeed.length != feed.length) return false; for (int i = 0; i < expectedFeed.length; i++) { Map<String, Object> expected = (HashMap<String, Object>) Manager.getObjectMapper().readValue(expectedFeed[i], Map.class); Map<String, Object> item = (HashMap<String, Object>) Manager.getObjectMapper().readValue(feed[i], Map.class); if (!expected.equals(item)) return false; } return true; } // https://github.com/couchbase/couchbase-lite-java-core/issues/1540 public void testReplicateWithError() throws Exception { // create MockWebServer and custom dispatcher MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getMockWebServer(dispatcher); dispatcher.setServerType(MockDispatcher.ServerType.SYNC_GW); try { { // mock documents to be pulled MockDocumentGet.MockDocument mockDoc1 = new MockDocumentGet.MockDocument("doc1", "1-5e38", 1); mockDoc1.setJsonMap(MockHelper.generateRandomJsonMap()); // checkpoint GET response w/ 404 MockResponse fakeCheckpointResponse = new MockResponse(); MockHelper.set404NotFoundJson(fakeCheckpointResponse); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, fakeCheckpointResponse); // _changes response MockChangesFeed mockChangesFeed = new MockChangesFeed(); mockChangesFeed.add(new MockChangesFeed.MockChangedDoc(mockDoc1)); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHANGES, mockChangesFeed.generateMockResponse()); // Empty _all_docs response to pass unit tests dispatcher.enqueueResponse(MockHelper.PATH_REGEX_ALL_DOCS, new MockDocumentAllDocs()); // doc1 response -> bad request MockResponse mockResponse = new MockResponse().setResponseCode(400); dispatcher.enqueueResponse(mockDoc1.getDocPathRegex(), mockResponse); // _bulk_get response MockDocumentBulkGet mockBulkGet = new MockDocumentBulkGet(); mockBulkGet.addDocument(mockDoc1); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_BULK_GET, mockBulkGet); // respond to all PUT Checkpoint requests MockCheckpointPut mockCheckpointPut = new MockCheckpointPut(); mockCheckpointPut.setSticky(true); mockCheckpointPut.setDelayMs(500); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, mockCheckpointPut); } // start mock server server.start(); Map<String, Object> replicateJsonMap = getPullReplicationProperties(server.url("/db").url()); Log.i(TAG, "replicateJsonMap: " + replicateJsonMap); Map<String, Object> result = (Map<String, Object>) sendBody("POST", "/_replicate", replicateJsonMap, 400, null); Log.i(TAG, "result: " + result); assertFalse(result.containsKey("ok")); assertFalse(result.containsKey("session_id")); assertTrue(result.containsKey("error")); assertEquals("Client Error", (String) result.get("error")); assertEquals(0, database.getDocumentCount()); } finally { assertTrue(MockHelper.shutdown(server, dispatcher)); } } /** * Router_tests.m * - (void) test_deleteDoc * * See: https://github.com/couchbase/couchbase-lite-java-core/issues/1573 */ public void failing_test_deleteDoc(){ // Create db: send("PUT", "/db", Status.CREATED, null); // Create doc: Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("foo", "bar"); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); assertTrue(revID.startsWith("1-")); // Delete doc: String path = String.format(Locale.ENGLISH, "/db/doc1?rev=%s", revID); result = (Map<String, Object>) sendBody("DELETE", path, null, Status.OK, null); revID = (String) result.get("rev"); assertTrue(revID.startsWith("2-")); // Get the deletes doc: Map<String, Object> responseBody = new HashMap<String, Object>(); responseBody.put("error", "not_found"); responseBody.put("reason", "deleted"); responseBody.put("status", 404); send("GET", "/db/doc1", Status.DELETED, responseBody); } /** * Router_tests.m * - (void) test_purgeDoc * * See: https://github.com/couchbase/couchbase-lite-java-core/issues/1573 */ public void failing_test_purgeDoc(){ // Create db: send("PUT", "/db", Status.CREATED, null); // Create doc: Map<String, Object> doc1 = new HashMap<String, Object>(); doc1.put("foo", "bar"); Map<String, Object> result = (Map<String, Object>) sendBody("PUT", "/db/doc1", doc1, Status.CREATED, null); String revID = (String) result.get("rev"); assertTrue(revID.startsWith("1-")); // Purge doc: Map<String, Object> reqBody = new HashMap<String, Object>(); reqBody.put("doc1", Arrays.asList(revID)); Map<String, Object> expectedResp = new HashMap<String, Object>(); expectedResp.put("purged", reqBody); sendBody("POST", "/db/_purge", reqBody , Status.OK, expectedResp); // Get the purged doc: Map<String, Object> responseBody = new HashMap<String, Object>(); responseBody.put("error", "not_found"); responseBody.put("reason", "missing"); responseBody.put("status", 404); send("GET", "/db/doc1", Status.NOT_FOUND, responseBody); } // https://github.com/couchbase/couchbase-lite-android/issues/1139 public void testViewUpdate() throws CouchbaseLiteException { View.setCompiler(new JavaScriptViewCompiler()); Database.setFilterCompiler(new JavaScriptReplicationFilterCompiler()); Map<String, Object> resp; // create `db` Database DatabaseOptions ops = new DatabaseOptions(); ops.setCreate(true); Database db = manager.openDatabase("db", ops); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("type", "person"); properties.put("name", "mick"); createDocWithProperties(properties, db); properties = new HashMap<String, Object>(); properties.put("type", "person"); properties.put("name", "keef"); createDocWithProperties(properties, db); properties.put("type", "aardvark"); properties.put("name", "cerebus"); createDocWithProperties(properties, db); Map<String, Object> viewsBody1 = new HashMap<String, Object>(); Map<String, Object> viewBody1 = new HashMap<String, Object>(); Map<String, Object> mapBody1 = new HashMap<String, Object>(); mapBody1.put("map", "function(doc) { if (doc.type == 'person') { emit(doc.name, null) } }"); viewBody1.put("view", mapBody1); viewsBody1.put("views", viewBody1); resp = (Map<String, Object>)sendBody("PUT", "/db/_design/design", viewsBody1, Status.CREATED, null); String revID1 = (String)resp.get("rev"); resp = (Map<String, Object>)send("GET", "/db/_design/design/_view/view", Status.OK, null); assertEquals(2, resp.get("total_rows")); View view = db.getView("design/view"); String mapReduceVersion1 = view.getMapVersion(); Map<String, Object> viewsBody2 = new HashMap<String, Object>(); Map<String, Object> viewBody2 = new HashMap<String, Object>(); Map<String, Object> mapBody2 = new HashMap<String, Object>(); mapBody2.put("map", "function(doc) { if (doc.type == 'aardvark') { emit(doc.name, null) } }"); viewBody2.put("view", mapBody2); viewsBody2.put("views", viewBody2); String urlStr = String.format(Locale.ENGLISH, "/db/_design/design?rev=%s", revID1); resp = (Map<String, Object>)sendBody("PUT", urlStr, viewsBody2, Status.CREATED, null); String revID2 = (String)resp.get("rev"); resp = (Map<String, Object>)send("GET", "/db/_design/design/_view/view", Status.OK, null); assertEquals(1, resp.get("total_rows")); view = db.getView("design/view"); String mapReduceVersion2 = view.getMapVersion(); assertFalse(mapReduceVersion1.equals(mapReduceVersion2)); } }