/** * 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.syncgateway; import com.couchbase.lite.CouchbaseLiteException; import com.couchbase.lite.Database; import com.couchbase.lite.Document; import com.couchbase.lite.LiteTestCaseWithDB; import com.couchbase.lite.Manager; import com.couchbase.lite.UnsavedRevision; import com.couchbase.lite.replicator.Replication; import com.couchbase.lite.util.Log; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; /** * Created by hideki on 2/25/16. */ public class PushReplTest extends LiteTestCaseWithDB { public static final String TAG = "PushReplTest"; @Override protected void setUp() throws Exception { if (!syncgatewayTestsEnabled()) { return; } super.setUp(); } public void testPushSingleDocument() throws Exception { if (!syncgatewayTestsEnabled()) { return; } // Single document without attachment _testPushSingleDocument(false, -1); // None // Single document with small attachment _testPushSingleDocument(true, 10); // 10 B // Single document with large attachment _testPushSingleDocument(true, 1024 * 10); // 10KB // Single document with larger attachment _testPushSingleDocument(true, 1024 * 100); // 100KB } public void _testPushSingleDocument(boolean attachment, int attachmentSize) throws Exception{ if (!syncgatewayTestsEnabled()) { return; } // create document Document doc = database.createDocument(); Map<String, Object> props = new HashMap<String, Object>(); props.put("key", "Hello World!"); doc.putProperties(props); String docID = doc.getId(); // add attachment if(attachment) { char[] chars = new char[attachmentSize]; Arrays.fill(chars, 'a'); final String content = new String(chars); ByteArrayInputStream body = new ByteArrayInputStream(content.getBytes("UTF-8")); try { UnsavedRevision newRev = doc.createRevision(); newRev.setAttachment("attachment1", "text/plain; charset=utf-8", body); newRev.save(); } finally { body.close(); } } // start push replicator pushData(getReplicationURL()); Map<String, Object> data = getDocByID(docID); assertEquals("Hello World!", data.get("key")); // check attachment if(attachment){ Map<String, Object> attachments = (Map<String, Object>) getDocByID(docID).get("_attachments"); assertTrue(attachments.containsKey("attachment1")); } } /** * Note: For test, needs to restart sync gateway. Default sync gateway does not allow * to access admin port from non-local to delete db. */ public void testPushRepl() throws Exception { if (!syncgatewayTestsEnabled()) { return; } final int INIT_DOCS = 100; final int TOTAL_DOCS = 400; // initial 100 docs for (int i = 0; i < INIT_DOCS; i++) { Document doc = database.getDocument("doc-" + String.format(Locale.ENGLISH, "%03d", i)); Map<String, Object> props = new HashMap<String, Object>(); props.put("key", i); try { doc.putProperties(props); } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to create new doc", e); fail(e.getMessage()); } } final CountDownLatch latch = new CountDownLatch(1); // thread creates documents during replication. Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = INIT_DOCS; i < TOTAL_DOCS; i++) { Document doc = database.getDocument("doc-" + String.format(Locale.ENGLISH, "%03d", i)); Map<String, Object> props = new HashMap<String, Object>(); props.put("key", i); try { doc.putProperties(props); } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to create new doc", e); fail(e.getMessage()); } } latch.countDown(); } }); thread.start(); pushData(getReplicationURL()); // wait till thread finishes to create all docs. latch.await(); // verify total document number on remote assertEquals(TOTAL_DOCS, ((Number) getAllDocs().get("total_rows")).intValue()); String docID = "doc-" + String.format(Locale.ENGLISH, "%03d", 100); Document doc = database.getDocument(docID); //Add Attachment 1 StringBuffer sb = new StringBuffer(); int size = 50 * 1024; for (int i = 0; i < size; i++) { sb.append("a"); } ByteArrayInputStream body = new ByteArrayInputStream(sb.toString().getBytes()); UnsavedRevision newRev = doc.createRevision(); newRev.setAttachment("attachment1", "text/plain; charset=utf-8", body); newRev.save(); // start push replicator pushData(getReplicationURL()); Map<String, Object> attachments = (Map<String, Object>) getDocByID(docID).get("_attachments"); assertTrue(attachments.containsKey("attachment1")); //Add Attachment 2 StringBuffer sb2 = new StringBuffer(); int size2 = 50 * 1024; for (int i = 0; i < size2; i++) { sb.append("b"); } ByteArrayInputStream body2 = new ByteArrayInputStream(sb.toString().getBytes()); UnsavedRevision newRev2 = doc.createRevision(); newRev2.setAttachment("attachment2", "text/plain; charset=utf-8", body2); newRev2.save(); pushData(getReplicationURL()); // verify total document number on remote Map<String, Object> attachmentsV2 = (Map<String, Object>) getDocByID(docID).get("_attachments"); assertTrue(attachmentsV2.containsKey("attachment2")); } void pushData(URL remote) throws Exception { // start push replicator final CountDownLatch idle = new CountDownLatch(1); final CountDownLatch stop = new CountDownLatch(1); ReplicationIdleObserver idleObserver = new ReplicationIdleObserver(idle); ReplicationFinishedObserver stopObserver = new ReplicationFinishedObserver(stop); Replication push = database.createPushReplication(remote); push.setContinuous(true); push.addChangeListener(idleObserver); push.addChangeListener(stopObserver); push.start(); // wait till push become idle state idle.await(); // stop push push.stop(); // stop till push become stopped state stop.await(); // give sync gateway to process all. Thread.sleep(2*1000); } // GET /{db}/_all_docs Map<String, Object> getAllDocs() throws IOException { HttpURLConnection connection = null; try { URL url = new URL(System.getProperty("replicationUrl") + "/_all_docs"); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); InputStream in = connection.getInputStream(); try { return Manager.getObjectMapper().readValue(in, Map.class); } finally { if (in != null) in.close(); } } finally { if (connection != null) connection.disconnect(); } } Map<String, Object> getDocByID(String docId) throws IOException { HttpURLConnection connection = null; try { URL url = new URL(System.getProperty("replicationUrl") + "/" + docId); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); InputStream in = connection.getInputStream(); try { return Manager.getObjectMapper().readValue(in, Map.class); } finally { if (in != null) in.close(); } } finally { if (connection != null) connection.disconnect(); } } }