/** * 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.replicator; import com.couchbase.lite.CouchbaseLiteException; import com.couchbase.lite.Document; import com.couchbase.lite.LiteTestCaseWithDB; import com.couchbase.lite.UnsavedRevision; import com.couchbase.lite.mockserver.MockBulkDocs; import com.couchbase.lite.mockserver.MockCheckpointPut; import com.couchbase.lite.mockserver.MockDispatcher; import com.couchbase.lite.mockserver.MockDocumentGet; import com.couchbase.lite.mockserver.MockDocumentPut; import com.couchbase.lite.mockserver.MockHelper; import com.couchbase.lite.mockserver.MockRevsDiff; import com.couchbase.lite.support.MultipartReader; import com.couchbase.lite.support.MultipartReaderDelegate; import com.couchbase.lite.util.Utils; import java.util.Arrays; import java.util.Locale; import java.util.Map; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; /** * Created by hideki on 5/11/15. */ public class PushReplicationTest extends LiteTestCaseWithDB { /** * This test is too slow to run with ARM Emulator API 19 on Jenkins. * As default, test is disabled. * https://github.com/couchbase/couchbase-lite-java-core/issues/1108 */ public void manualTestPushWithManyAttachment() throws Exception { // add document String docId = "doc1"; String docPathRegex = String.format(Locale.ENGLISH, "/db/%s.*", docId); String docAttachName = "attachment_%d.txt"; String assetAttachName = "attach.txt"; String contentType = "application/octet-stream"; int numAttachments = 1025; Document doc = createDocumentForPushReplication( docId, assetAttachName, docAttachName, numAttachments, contentType); // create mockwebserver and custom dispatcher MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getMockWebServer(dispatcher); dispatcher.setServerType(MockDispatcher.ServerType.SYNC_GW); try { server.start(); // checkpoint GET response w/ 404 + respond to all PUT Checkpoint requests MockCheckpointPut mockCheckpointPut = new MockCheckpointPut(); mockCheckpointPut.setSticky(true); mockCheckpointPut.setDelayMs(50); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, mockCheckpointPut); // _revs_diff response -- everything missing MockRevsDiff mockRevsDiff = new MockRevsDiff(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_REVS_DIFF, mockRevsDiff); // _bulk_docs response -- everything stored MockBulkDocs mockBulkDocs = new MockBulkDocs(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_BULK_DOCS, mockBulkDocs); // doc PUT responses for docs with attachments MockDocumentPut mockDocPut = new MockDocumentPut().setDocId(docId) .setRev(doc.getCurrentRevisionId()); dispatcher.enqueueResponse(docPathRegex, mockDocPut.generateMockResponse()); // run replication Replication replication = database.createPushReplication(server.url("/db").url()); replication.setContinuous(false); runReplication(replication); // make assertions about outgoing requests from replicator -> mock RecordedRequest getCheckpointRequest = dispatcher.takeRequest( MockHelper.PATH_REGEX_CHECKPOINT); assertTrue(getCheckpointRequest.getMethod().equals("GET")); assertTrue(getCheckpointRequest.getPath().matches( MockHelper.PATH_REGEX_CHECKPOINT)); RecordedRequest revsDiffRequest = dispatcher.takeRequest( MockHelper.PATH_REGEX_REVS_DIFF); assertTrue(MockHelper.getUtf8Body(revsDiffRequest).contains(docId)); RecordedRequest docPutRequest = dispatcher.takeRequest(docPathRegex); assertNotNull(docPutRequest); CustomMultipartReaderDelegate delegate = new CustomMultipartReaderDelegate(); MultipartReader reader = new MultipartReader( docPutRequest.getHeader("Content-Type"), delegate); reader.appendData(docPutRequest.getBody().readByteArray()); assertTrue(delegate.json.contains(docId)); } finally { assertTrue(MockHelper.shutdown(server, dispatcher)); } } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/614 * <p/> * NOTE: To test json length is less than RemoteRequest.MIN_JSON_LENGTH_TO_COMPRESS * Need to modify MIN_JSON_LENGTH_TO_COMPRESS value to larger than 300. */ public void testPushSmallDocWithAttachment() throws Exception { // add document String docId = "doc1"; String docPathRegex = String.format(Locale.ENGLISH, "/db/%s.*", docId); String docAttachName = "attachment.png"; String contentType = "image/png"; Document doc = createDocumentForPushReplication(docId, docAttachName, contentType); // create mockwebserver and custom dispatcher MockDispatcher dispatcher = new MockDispatcher(); MockWebServer server = MockHelper.getMockWebServer(dispatcher); dispatcher.setServerType(MockDispatcher.ServerType.SYNC_GW); try { server.start(); // checkpoint GET response w/ 404 + respond to all PUT Checkpoint requests MockCheckpointPut mockCheckpointPut = new MockCheckpointPut(); mockCheckpointPut.setSticky(true); mockCheckpointPut.setDelayMs(50); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_CHECKPOINT, mockCheckpointPut); // _revs_diff response -- everything missing MockRevsDiff mockRevsDiff = new MockRevsDiff(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_REVS_DIFF, mockRevsDiff); // _bulk_docs response -- everything stored MockBulkDocs mockBulkDocs = new MockBulkDocs(); dispatcher.enqueueResponse(MockHelper.PATH_REGEX_BULK_DOCS, mockBulkDocs); // doc PUT responses for docs with attachments MockDocumentPut mockDocPut = new MockDocumentPut().setDocId(docId) .setRev(doc.getCurrentRevisionId()); dispatcher.enqueueResponse(docPathRegex, mockDocPut.generateMockResponse()); // run replication Replication replication = database.createPushReplication(server.url("/db").url()); replication.setContinuous(false); runReplication(replication); // make assertions about outgoing requests from replicator -> mock RecordedRequest getCheckpointRequest = dispatcher.takeRequest( MockHelper.PATH_REGEX_CHECKPOINT); assertNotNull(getCheckpointRequest); assertTrue(getCheckpointRequest.getMethod().equals("GET")); assertTrue(getCheckpointRequest.getPath().matches( MockHelper.PATH_REGEX_CHECKPOINT)); RecordedRequest revsDiffRequest = dispatcher.takeRequest( MockHelper.PATH_REGEX_REVS_DIFF); assertNotNull(revsDiffRequest); assertTrue(MockHelper.getUtf8Body(revsDiffRequest).contains(docId)); RecordedRequest docPutRequest = dispatcher.takeRequest(docPathRegex); assertNotNull(docPutRequest); CustomMultipartReaderDelegate delegate = new CustomMultipartReaderDelegate(); MultipartReader reader = new MultipartReader( docPutRequest.getHeader("Content-Type"), delegate); reader.appendData(docPutRequest.getBody().readByteArray()); assertTrue(delegate.json.contains(docId)); byte[] attachmentBytes = MockDocumentGet.getAssetByteArray(docAttachName); assertTrue(Arrays.equals(attachmentBytes, delegate.attachment)); } finally { assertTrue(MockHelper.shutdown(server, dispatcher)); } } protected Document createDocumentForPushReplication(String docId, String attachmentFileName, String attachmentContentType) throws CouchbaseLiteException { Document document = database.getDocument(docId); UnsavedRevision revision = document.createRevision(); if (attachmentFileName != null) { revision.setAttachment( attachmentFileName, attachmentContentType, getAsset(attachmentFileName) ); } revision.save(); return document; } protected Document createDocumentForPushReplication( String docId, String assetAttachmentFileName, String attachmentFileNameTemplate, int numAttachments, String attachmentContentType) throws CouchbaseLiteException { Document document = database.getDocument(docId); UnsavedRevision revision = document.createRevision(); if (attachmentFileNameTemplate != null) { for (int i = 0; i < numAttachments; i++) { String attachmentFileName = String.format(Locale.ENGLISH, attachmentFileNameTemplate, i); revision.setAttachment( attachmentFileName, attachmentContentType, getAsset(assetAttachmentFileName) ); } } revision.save(); return document; } class CustomMultipartReaderDelegate implements MultipartReaderDelegate { public byte[] attachment = null; public String json = null; public boolean gzipped = false; public boolean bJson = false; @Override public void startedPart(Map<String, String> headers) { gzipped = headers.get("Content-Encoding") != null && headers.get("Content-Encoding").contains("gzip"); bJson = headers.get("Content-Type") != null && headers.get("Content-Type").contains("application/json"); } @Override public void appendToPart(byte[] data) { try { if (gzipped && bJson) { this.json = new String(Utils.decompressByGzip(data), "UTF-8"); } else if (!gzipped && bJson) { this.json = new String(data, "UTF-8"); } else if (gzipped) { this.attachment = Utils.decompressByGzip(data); } else { this.attachment = data; } } catch (Exception ex) { } } @Override public void appendToPart(final byte[] data, int off, int len) { byte[] b = Arrays.copyOfRange(data, off, len - off); appendToPart(b); } @Override public void finishedPart() { } } }