Android Open Source - couchbase-lite-android Attachments Test






From Project

Back to project page couchbase-lite-android.

License

The source code is released under:

Apache License

If you think the Android project couchbase-lite-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Original iOS version by  Jens Alfke/*  w  ww . j  a  v a2 s . c  o  m*/
 * Ported to Android by Marty Schoch
 *
 * Copyright (c) 2012 Couchbase, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 */

package com.couchbase.lite;

import com.couchbase.lite.internal.AttachmentInternal;
import com.couchbase.lite.internal.RevisionInternal;
import com.couchbase.lite.storage.ContentValues;
import com.couchbase.lite.storage.SQLException;
import com.couchbase.lite.support.Base64;
import com.couchbase.lite.util.Log;
import com.couchbase.lite.util.TextUtils;

import junit.framework.Assert;

import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AttachmentsTest extends LiteTestCase {

    public static final String TAG = "Attachments";

    @SuppressWarnings("unchecked")
    public void testAttachments() throws Exception {

        String testAttachmentName = "test_attachment";

        BlobStore attachments = database.getAttachments();

        Assert.assertEquals(0, attachments.count());
        Assert.assertEquals(new HashSet<Object>(), attachments.allKeys());

        Status status = new Status();
        Map<String, Object> rev1Properties = new HashMap<String, Object>();
        rev1Properties.put("foo", 1);
        rev1Properties.put("bar", false);
        RevisionInternal rev1 = database.putRevision(new RevisionInternal(rev1Properties, database), null, false, status);

        Assert.assertEquals(Status.CREATED, status.getCode());

        byte[] attach1 = "This is the body of attach1".getBytes();
        database.insertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1), rev1.getSequence(), testAttachmentName, "text/plain", rev1.getGeneration());
        Assert.assertEquals(Status.CREATED, status.getCode());

        //We must set the no_attachments column for the rev to false, as we are using an internal
        //private API call above (database.insertAttachmentForSequenceWithNameAndType) which does
        //not set the no_attachments column on revs table
        try {
            ContentValues args = new ContentValues();
            args.put("no_attachments=", false);
            database.getDatabase().update("revs", args, "sequence=?", new String[]{String.valueOf(rev1.getSequence())});
        } catch (SQLException e) {
            Log.e(Database.TAG, "Error setting rev1 no_attachments to false", e);
            throw new CouchbaseLiteException(Status.INTERNAL_SERVER_ERROR);
        }

        Attachment attachment = database.getAttachmentForSequence(rev1.getSequence(), testAttachmentName);
        Assert.assertEquals("text/plain", attachment.getContentType());
        InputStream is = attachment.getContent();
        byte[] data = IOUtils.toByteArray(is);
        is.close();
        Assert.assertTrue(Arrays.equals(attach1, data));

        Map<String, Object> innerDict = new HashMap<String, Object>();
        innerDict.put("content_type", "text/plain");
        innerDict.put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=");
        innerDict.put("length", 27);
        innerDict.put("stub", true);
        innerDict.put("revpos", 1);
        Map<String, Object> attachmentDict = new HashMap<String, Object>();
        attachmentDict.put(testAttachmentName, innerDict);

        Map<String, Object> attachmentDictForSequence = database.getAttachmentsDictForSequenceWithContent(rev1.getSequence(), EnumSet.noneOf(Database.TDContentOptions.class));
        Assert.assertEquals(attachmentDict, attachmentDictForSequence);

        RevisionInternal gotRev1 = database.getDocumentWithIDAndRev(rev1.getDocId(), rev1.getRevId(), EnumSet.noneOf(Database.TDContentOptions.class));
        Map<String, Object> gotAttachmentDict = (Map<String, Object>) gotRev1.getProperties().get("_attachments");
        Assert.assertEquals(attachmentDict, gotAttachmentDict);

        // Check the attachment dict, with attachments included:
        innerDict.remove("stub");
        innerDict.put("data", Base64.encodeBytes(attach1));
        attachmentDictForSequence = database.getAttachmentsDictForSequenceWithContent(rev1.getSequence(), EnumSet.of(Database.TDContentOptions.TDIncludeAttachments));
        Assert.assertEquals(attachmentDict, attachmentDictForSequence);

        gotRev1 = database.getDocumentWithIDAndRev(rev1.getDocId(), rev1.getRevId(), EnumSet.of(Database.TDContentOptions.TDIncludeAttachments));
        gotAttachmentDict = (Map<String, Object>) gotRev1.getProperties().get("_attachments");
        Assert.assertEquals(attachmentDict, gotAttachmentDict);


        // Add a second revision that doesn't update the attachment:
        Map<String, Object> rev2Properties = new HashMap<String, Object>();
        rev2Properties.put("_id", rev1.getDocId());
        rev2Properties.put("foo", 2);
        rev2Properties.put("bazz", false);
        RevisionInternal rev2 = database.putRevision(new RevisionInternal(rev2Properties, database), rev1.getRevId(), false, status);
        Assert.assertEquals(Status.CREATED, status.getCode());

        database.copyAttachmentNamedFromSequenceToSequence(testAttachmentName, rev1.getSequence(), rev2.getSequence());

        // Add a third revision of the same document:
        Map<String, Object> rev3Properties = new HashMap<String, Object>();
        rev3Properties.put("_id", rev2.getDocId());
        rev3Properties.put("foo", 2);
        rev3Properties.put("bazz", false);
        RevisionInternal rev3 = database.putRevision(new RevisionInternal(rev3Properties, database), rev2.getRevId(), false, status);
        Assert.assertEquals(Status.CREATED, status.getCode());

        byte[] attach2 = "<html>And this is attach2</html>".getBytes();
        database.insertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach2), rev3.getSequence(), testAttachmentName, "text/html", rev2.getGeneration());

        // Check the 2nd revision's attachment:
        Attachment attachment2 = database.getAttachmentForSequence(rev2.getSequence(), testAttachmentName);

        Assert.assertEquals("text/plain", attachment2.getContentType());
        InputStream is2 = attachment2.getContent();
        data = IOUtils.toByteArray(is2);
        is2.close();
        Assert.assertTrue(Arrays.equals(attach1, data));

        // Check the 3rd revision's attachment:
        Attachment attachment3 = database.getAttachmentForSequence(rev3.getSequence(), testAttachmentName);
        Assert.assertEquals("text/html", attachment3.getContentType());
        InputStream is3 = attachment3.getContent();
        data = IOUtils.toByteArray(is3);
        is3.close();
        Assert.assertTrue(Arrays.equals(attach2, data));

        Map<String, Object> attachmentDictForRev3 = (Map<String, Object>) database.getAttachmentsDictForSequenceWithContent(rev3.getSequence(), EnumSet.noneOf(Database.TDContentOptions.class)).get(testAttachmentName);
        if (attachmentDictForRev3.containsKey("follows")) {
            if (((Boolean) attachmentDictForRev3.get("follows")).booleanValue() == true) {
                throw new RuntimeException("Did not expected attachment dict 'follows' key to be true");
            } else {
                throw new RuntimeException("Did not expected attachment dict to have 'follows' key");
            }
        }

        // Examine the attachment store:
        Assert.assertEquals(2, attachments.count());
        Set<BlobKey> expected = new HashSet<BlobKey>();
        expected.add(BlobStore.keyForBlob(attach1));
        expected.add(BlobStore.keyForBlob(attach2));

        Assert.assertEquals(expected, attachments.allKeys());

        database.compact();  // This clears the body of the first revision
        Assert.assertEquals(1, attachments.count());

        Set<BlobKey> expected2 = new HashSet<BlobKey>();
        expected2.add(BlobStore.keyForBlob(attach2));
        Assert.assertEquals(expected2, attachments.allKeys());
    }

    @SuppressWarnings("unchecked")
    /**
     ObjectiveC equivalent: CBL_Database_Tests.CBL_Database_Attachments()
     */
    public void testPutLargeAttachment() throws Exception {

        String testAttachmentName = "test_attachment";

        BlobStore attachments = database.getAttachments();
        attachments.deleteBlobs();
        Assert.assertEquals(0, attachments.count());

        Status status = new Status();
        Map<String, Object> rev1Properties = new HashMap<String, Object>();
        rev1Properties.put("foo", 1);
        rev1Properties.put("bar", false);
        RevisionInternal rev1 = database.putRevision(new RevisionInternal(rev1Properties, database), null, false, status);

        Assert.assertEquals(Status.CREATED, status.getCode());

        StringBuffer largeAttachment = new StringBuffer();
        for (int i = 0; i < Database.kBigAttachmentLength; i++) {
            largeAttachment.append("big attachment!");
        }
        byte[] attach1 = largeAttachment.toString().getBytes();
        database.insertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach1),
                rev1.getSequence(), testAttachmentName, "text/plain", rev1.getGeneration());

        Attachment attachment = database.getAttachmentForSequence(rev1.getSequence(), testAttachmentName);
        Assert.assertEquals("text/plain", attachment.getContentType());
        InputStream is = attachment.getContent();
        byte[] data = IOUtils.toByteArray(is);
        is.close();
        Assert.assertTrue(Arrays.equals(attach1, data));

        EnumSet<Database.TDContentOptions> contentOptions = EnumSet.of(
                Database.TDContentOptions.TDIncludeAttachments,
                Database.TDContentOptions.TDBigAttachmentsFollow
        );

        Map<String, Object> attachmentDictForSequence = database.getAttachmentsDictForSequenceWithContent(
                rev1.getSequence(),
                contentOptions
        );

        Map<String, Object> innerDict = (Map<String, Object>) attachmentDictForSequence.get(testAttachmentName);

        if (innerDict.containsKey("stub")) {
            if (((Boolean) innerDict.get("stub")).booleanValue() == true) {
                throw new RuntimeException("Did not expected attachment dict 'stub' key to be true");
            } else {
                throw new RuntimeException("Did not expected attachment dict to have 'stub' key");
            }
        }

        if (!innerDict.containsKey("follows")) {
            throw new RuntimeException("Expected attachment dict to have 'follows' key");
        }

        RevisionInternal rev1WithAttachments = database.getDocumentWithIDAndRev(rev1.getDocId(), rev1.getRevId(), contentOptions);
        // Map<String,Object> rev1PropertiesPrime = rev1WithAttachments.getProperties();
        // rev1PropertiesPrime.put("foo", 2);


        Map<String, Object> rev1WithAttachmentsProperties = rev1WithAttachments.getProperties();

        Map<String, Object> rev2Properties = new HashMap<String, Object>();
        rev2Properties.put("_id", rev1WithAttachmentsProperties.get("_id"));
        rev2Properties.put("foo", 2);

        RevisionInternal newRev = new RevisionInternal(rev2Properties, database);
        RevisionInternal rev2 = database.putRevision(newRev, rev1WithAttachments.getRevId(), false, status);
        Assert.assertEquals(Status.CREATED, status.getCode());

        database.copyAttachmentNamedFromSequenceToSequence(
                testAttachmentName,
                rev1WithAttachments.getSequence(),
                rev2.getSequence());

        // Check the 2nd revision's attachment:
        Attachment rev2FetchedAttachment = database.getAttachmentForSequence(rev2.getSequence(), testAttachmentName);
        Assert.assertEquals(attachment.getLength(), rev2FetchedAttachment.getLength());
        Assert.assertEquals(attachment.getMetadata(), rev2FetchedAttachment.getMetadata());
        Assert.assertEquals(attachment.getContentType(), rev2FetchedAttachment.getContentType());
        // Because of how getAttachmentForSequence works rev2FetchedAttachment has an open stream as a body, we have to close it.
        rev2FetchedAttachment.getContent().close();

        // Add a third revision of the same document:
        Map<String, Object> rev3Properties = new HashMap<String, Object>();
        rev3Properties.put("_id", rev2.getProperties().get("_id"));
        rev3Properties.put("foo", 3);
        rev3Properties.put("baz", false);

        RevisionInternal rev3 = new RevisionInternal(rev3Properties, database);
        rev3 = database.putRevision(rev3, rev2.getRevId(), false, status);
        Assert.assertEquals(Status.CREATED, status.getCode());

        byte[] attach3 = "<html><blink>attach3</blink></html>".getBytes();
        database.insertAttachmentForSequenceWithNameAndType(new ByteArrayInputStream(attach3),
                rev3.getSequence(), testAttachmentName, "text/html", rev3.getGeneration());

        // Check the 3rd revision's attachment:
        Attachment rev3FetchedAttachment = database.getAttachmentForSequence(rev3.getSequence(), testAttachmentName);

        InputStream isRev3 = rev3FetchedAttachment.getContent();
        data = IOUtils.toByteArray(isRev3);
        isRev3.close();
        Assert.assertTrue(Arrays.equals(attach3, data));
        Assert.assertEquals("text/html", rev3FetchedAttachment.getContentType());

        // TODO: why doesn't this work?
        // Assert.assertEquals(attach3.length, rev3FetchedAttachment.getLength());

        Set<BlobKey> blobKeys = database.getAttachments().allKeys();
        Assert.assertEquals(2, blobKeys.size());
        database.compact();
        blobKeys = database.getAttachments().allKeys();
        Assert.assertEquals(1, blobKeys.size());


    }

    @SuppressWarnings("unchecked")
    public void testPutAttachment() throws CouchbaseLiteException {

        String testAttachmentName = "test_attachment";
        BlobStore attachments = database.getAttachments();
        attachments.deleteBlobs();
        Assert.assertEquals(0, attachments.count());

        // Put a revision that includes an _attachments dict:
        byte[] attach1 = "This is the body of attach1".getBytes();
        String base64 = Base64.encodeBytes(attach1);

        Map<String, Object> attachment = new HashMap<String, Object>();
        attachment.put("content_type", "text/plain");
        attachment.put("data", base64);
        Map<String, Object> attachmentDict = new HashMap<String, Object>();
        attachmentDict.put(testAttachmentName, attachment);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("foo", 1);
        properties.put("bar", false);
        properties.put("_attachments", attachmentDict);

        RevisionInternal rev1 = database.putRevision(new RevisionInternal(properties, database), null, false);

        // Examine the attachment store:
        Assert.assertEquals(1, attachments.count());

        // Get the revision:
        RevisionInternal gotRev1 = database.getDocumentWithIDAndRev(rev1.getDocId(), rev1.getRevId(), EnumSet.noneOf(Database.TDContentOptions.class));
        Map<String, Object> gotAttachmentDict = (Map<String, Object>) gotRev1.getProperties().get("_attachments");

        Map<String, Object> innerDict = new HashMap<String, Object>();
        innerDict.put("content_type", "text/plain");
        innerDict.put("digest", "sha1-gOHUOBmIMoDCrMuGyaLWzf1hQTE=");
        innerDict.put("length", 27);
        innerDict.put("stub", true);
        innerDict.put("revpos", 1);

        Map<String, Object> expectAttachmentDict = new HashMap<String, Object>();
        expectAttachmentDict.put(testAttachmentName, innerDict);

        Assert.assertEquals(expectAttachmentDict, gotAttachmentDict);

        // Update the attachment directly:
        byte[] attachv2 = "Replaced body of attach".getBytes();
        boolean gotExpectedErrorCode = false;

        BlobStoreWriter blobWriter = new BlobStoreWriter(database.getAttachments());
        blobWriter.appendData(attachv2);
        blobWriter.finish();

        try {
            database.updateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, rev1.getDocId(), null);
        } catch (CouchbaseLiteException e) {
            gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.CONFLICT);
        }
        Assert.assertTrue(gotExpectedErrorCode);

        gotExpectedErrorCode = false;
        try {
            database.updateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, rev1.getDocId(), "1-bogus");
        } catch (CouchbaseLiteException e) {
            gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.CONFLICT);
        }
        Assert.assertTrue(gotExpectedErrorCode);

        gotExpectedErrorCode = false;
        RevisionInternal rev2 = null;
        try {
            rev2 = database.updateAttachment(testAttachmentName, blobWriter, "application/foo", AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, rev1.getDocId(), rev1.getRevId());
        } catch (CouchbaseLiteException e) {
            gotExpectedErrorCode = true;
        }
        Assert.assertFalse(gotExpectedErrorCode);

        Assert.assertEquals(rev1.getDocId(), rev2.getDocId());
        Assert.assertEquals(2, rev2.getGeneration());


        // Get the updated revision:
        RevisionInternal gotRev2 = database.getDocumentWithIDAndRev(rev2.getDocId(), rev2.getRevId(), EnumSet.noneOf(Database.TDContentOptions.class));
        attachmentDict = (Map<String, Object>) gotRev2.getProperties().get("_attachments");

        innerDict = new HashMap<String, Object>();
        innerDict.put("content_type", "application/foo");
        innerDict.put("digest", "sha1-mbT3208HI3PZgbG4zYWbDW2HsPk=");
        innerDict.put("length", 23);
        innerDict.put("stub", true);
        innerDict.put("revpos", 2);

        expectAttachmentDict.put(testAttachmentName, innerDict);

        Assert.assertEquals(expectAttachmentDict, attachmentDict);

        // Delete the attachment:
        gotExpectedErrorCode = false;
        try {
            database.updateAttachment("nosuchattach", null, null, AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, rev2.getDocId(), rev2.getRevId());
        } catch (CouchbaseLiteException e) {
            gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.NOT_FOUND);
        }
        Assert.assertTrue(gotExpectedErrorCode);

        gotExpectedErrorCode = false;
        try {
            database.updateAttachment("nosuchattach", null, null, AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, "nosuchdoc", "nosuchrev");
        } catch (CouchbaseLiteException e) {
            gotExpectedErrorCode = (e.getCBLStatus().getCode() == Status.NOT_FOUND);
        }
        Assert.assertTrue(gotExpectedErrorCode);


        RevisionInternal rev3 = database.updateAttachment(testAttachmentName, null, null, AttachmentInternal.AttachmentEncoding.AttachmentEncodingNone, rev2.getDocId(), rev2.getRevId());
        Assert.assertEquals(rev2.getDocId(), rev3.getDocId());
        Assert.assertEquals(3, rev3.getGeneration());

        // Get the updated revision:
        RevisionInternal gotRev3 = database.getDocumentWithIDAndRev(rev3.getDocId(), rev3.getRevId(), EnumSet.noneOf(Database.TDContentOptions.class));
        attachmentDict = (Map<String, Object>) gotRev3.getProperties().get("_attachments");
        Assert.assertNull(attachmentDict);

        database.close();
    }

    public void testStreamAttachmentBlobStoreWriter() {


        BlobStore attachments = database.getAttachments();

        BlobStoreWriter blobWriter = new com.couchbase.lite.BlobStoreWriter(attachments);
        String testBlob = "foo";
        blobWriter.appendData(new String(testBlob).getBytes());
        blobWriter.finish();

        String sha1Base64Digest = "sha1-C+7Hteo/D9vJXQ3UfzxbwnXaijM=";
        Assert.assertEquals(blobWriter.sHA1DigestString(), sha1Base64Digest);
        Assert.assertEquals(blobWriter.mD5DigestString(), "md5-rL0Y20zC+Fzt72VPzMSk2A==");

        // install it
        blobWriter.install();

        // look it up in blob store and make sure it's there
        BlobKey blobKey = new BlobKey(sha1Base64Digest);
        byte[] blob = attachments.blobForKey(blobKey);
        Assert.assertTrue(Arrays.equals(testBlob.getBytes(Charset.forName("UTF-8")), blob));
    }

    /**
     * https://github.com/couchbase/couchbase-lite-android/issues/134
     */
    public void testGetAttachmentBodyUsingPrefetch() throws CouchbaseLiteException, IOException {

        // add a doc with an attachment
        Document doc = database.createDocument();
        UnsavedRevision rev = doc.createRevision();

        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("foo", "bar");
        rev.setUserProperties(properties);

        final byte[] attachBodyBytes = "attach body".getBytes();
        Attachment attachment = new Attachment(
                new ByteArrayInputStream(attachBodyBytes),
                "text/plain"
        );

        final String attachmentName = "test_attachment.txt";
        rev.addAttachment(attachment, attachmentName);
        rev.save();

        // do query that finds that doc with prefetch
        View view = database.getView("aview");
        view.setMapReduce(new Mapper() {

            @Override
            public void map(Map<String, Object> document, Emitter emitter) {
                String id = (String) document.get("_id");
                emitter.emit(id, null);
            }
        }, null, "1");


        // try to get the attachment

        Query query = view.createQuery();
        query.setPrefetch(true);

        QueryEnumerator results = query.run();

        while (results.hasNext()) {

            QueryRow row = results.next();


            // This returns the revision just fine, but the sequence number
            // is set to 0.
            SavedRevision revision = row.getDocument().getCurrentRevision();

            List<String> attachments = revision.getAttachmentNames();

            // This returns an Attachment object which looks ok, except again
            // its sequence number is 0. The metadata property knows about
            // the length and mime type of the attachment. It also says
            // "stub" -> "true".
            Attachment attachmentRetrieved = revision.getAttachment(attachmentName);

            // This throws a CouchbaseLiteException with Status.NOT_FOUND.
            InputStream is = attachmentRetrieved.getContent();
            assertNotNull(is);
            byte[] attachmentDataRetrieved = TextUtils.read(is);
            is.close();
            String attachmentDataRetrievedString = new String(attachmentDataRetrieved);
            String attachBodyString = new String(attachBodyBytes);
            assertEquals(attachBodyString, attachmentDataRetrievedString);

        }

    }

    /**
     * Regression test for https://github.com/couchbase/couchbase-lite-java-core/issues/218
     */

    public void testGetAttachmentAfterItDeleted() throws CouchbaseLiteException, IOException {

        // add a doc with an attachment
        Document doc = database.createDocument();
        UnsavedRevision rev = doc.createRevision();

        final byte[] attachBodyBytes = "attach body".getBytes();
        Attachment attachment = new Attachment(
                new ByteArrayInputStream(attachBodyBytes),
                "text/plain"
        );

        String attachmentName = "test_delete_attachment.txt";
        rev.addAttachment(attachment, attachmentName);
        rev.save();

        UnsavedRevision rev1 = doc.createRevision();
        Attachment currentAttachment = rev1.getAttachment(attachmentName);
        assertNotNull(currentAttachment);

        rev1.removeAttachment(attachmentName);
        currentAttachment = rev1.getAttachment(attachmentName);
        assertNull(currentAttachment); // otherwise NullPointerException when currentAttachment.getMetadata()
        rev1.save();

        currentAttachment = doc.getCurrentRevision().getAttachment(attachmentName);
        assertNull(currentAttachment); // otherwise NullPointerException when currentAttachment.getMetadata()
    }


    /**
     * Regression test for https://github.com/couchbase/couchbase-lite-android-core/issues/70
     */
    public void testAttachmentDisappearsAfterSave() throws CouchbaseLiteException, IOException {

        // create a doc with an attachment
        Document doc = database.createDocument();
        String content = "This is a test attachment!";
        ByteArrayInputStream body = new ByteArrayInputStream(content.getBytes());
        UnsavedRevision rev = doc.createRevision();
        rev.setAttachment("index.html", "text/plain; charset=utf-8", body);
        rev.save();

        // make sure the doc's latest revision has the attachment
        Map<String, Object> attachments = (Map) doc.getCurrentRevision().getProperty("_attachments");
        assertNotNull(attachments);
        assertEquals(1, attachments.size());

        // make sure the rev has the attachment
        attachments = (Map) rev.getProperty("_attachments");
        assertNotNull(attachments);
        assertEquals(1, attachments.size());

        // create new properties to add
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("foo", "bar");

        // make sure the new rev still has the attachment
        UnsavedRevision rev2 = doc.createRevision();
        rev2.getProperties().putAll(properties);
        rev2.save();
        attachments = (Map) rev2.getProperty("_attachments");
        assertNotNull(attachments);
        assertEquals(1, attachments.size());

    }


    /**
     * attempt to reproduce https://github.com/couchbase/couchbase-lite-android/issues/328 &
     * https://github.com/couchbase/couchbase-lite-android/issues/325
     */
    public void testSetAttachmentsSequentially() throws CouchbaseLiteException, IOException {

        try {
            //Create rev1 of document with just properties
            Document doc = database.createDocument();
            String id = doc.getId();
            Map<String, Object> docProperties = new HashMap<String, Object>();
            docProperties.put("Iteration", 0);
            doc.putProperties(docProperties);

            UnsavedRevision rev = null;

            //Create a new revision with attachment1
            InputStream attachmentStream1 = getAsset("attachment.png");
            doc = database.getDocument(id);//not required
            byte[] jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
            Log.d(Database.TAG,"Doc _rev = %s",doc.getProperties().get("_rev"));
            Log.d(Database.TAG,"Doc properties = %s",new String(jsonb));
            rev = doc.createRevision();
            rev.setAttachment("attachment1", "image/png", attachmentStream1);
            rev.save();
            attachmentStream1.close();

            //Create a new revision updated properties
            doc = database.getDocument(id);//not required
            jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
            Log.d(Database.TAG,"Doc _rev = %s",doc.getProperties().get("_rev"));
            Log.d(Database.TAG,"Doc properties = %s",new String(jsonb));
            Map<String, Object> curProperties;
            curProperties = doc.getProperties();
            docProperties = new HashMap<String, Object>();
            docProperties.putAll(curProperties);
            docProperties.put("Iteration", 1);
            doc.putProperties(docProperties);


            //Create a new revision with attachment2
            InputStream attachmentStream2 = getAsset("attachment.png");
            doc = database.getDocument(id);//not required
            jsonb = Manager.getObjectMapper().writeValueAsBytes(doc.getProperties().get("_attachments"));
            Log.d(Database.TAG,"Doc _rev = %s",doc.getProperties().get("_rev"));
            Log.d(Database.TAG,"Doc properties = %s",new String(jsonb));
            rev = doc.createRevision();
            rev.setAttachment("attachment2", "image/png", attachmentStream2);
            rev.save();
            attachmentStream2.close();

            //Assert final document revision
            doc = database.getDocument(id);
            curProperties = doc.getProperties();
            assertEquals(4, curProperties.size());
            Map<String, Object> attachments = (Map<String, Object>) doc.getCurrentRevision().getProperty("_attachments");
            assertNotNull(attachments);
            assertEquals(2, attachments.size());
        } catch (CouchbaseLiteException e) {
            Log.e(Database.TAG, "Error adding attachment: "+e.getMessage(), e);
            fail();
        }

    }



    /**
     * attempt to reproduce https://github.com/couchbase/couchbase-lite-android/issues/328 &
     * https://github.com/couchbase/couchbase-lite-android/issues/325
     */
    public void failingTestSetAttachmentsSequentiallyInTransaction() throws CouchbaseLiteException, IOException {

        boolean success = database.runInTransaction(new TransactionalTask() {

            public boolean run() {

                try {
                    // add a doc with an attachment
                    Document doc = database.createDocument();
                    String id = doc.getId();

                    InputStream jsonStream = getAsset("300k.json");

                    Map<String, Object> docProperties = null;

                    docProperties = Manager.getObjectMapper().readValue(jsonStream, Map.class);


                    docProperties.put("Iteration", 0);

                    doc.putProperties(docProperties);

                    jsonStream.close();
                    UnsavedRevision rev = null;


                    for (int i = 0; i < 20; i++) {

                        InputStream attachmentStream1 = getAsset("attachment.png");

                        Log.e(Database.TAG, "TEST ITERATION " + i);
                        doc = database.getDocument(id);//not required
                        rev = doc.createRevision();
                        rev.setAttachment("attachment " + i * 5, "image/png", attachmentStream1);
                        rev.save();

                        attachmentStream1.close();

                        InputStream attachmentStream2 = getAsset("attachment.png");
                        doc = database.getDocument(id);//not required
                        rev = doc.createRevision();
                        rev.setAttachment("attachment " + i * 5 + 1, "image/png", attachmentStream2);
                        rev.save();

                        attachmentStream2.close();

                        InputStream attachmentStream3 = getAsset("attachment.png");
                        doc = database.getDocument(id);//not required
                        rev = doc.createRevision();
                        rev.setAttachment("attachment " + i * 5 + 2, "image/png", attachmentStream3);
                        rev.save();

                        attachmentStream3.close();

                        InputStream attachmentStream4 = getAsset("attachment.png");
                        doc = database.getDocument(id);//not required
                        rev = doc.createRevision();
                        rev.setAttachment("attachment " + i * 5 + 3, "image/png", attachmentStream4);
                        rev.save();

                        attachmentStream4.close();

                        InputStream attachmentStream5 = getAsset("attachment.png");
                        doc = database.getDocument(id);//not required
                        rev = doc.createRevision();
                        rev.setAttachment("attachment " + i * 5 + 4, "image/png", attachmentStream5);
                        rev.save();

                        attachmentStream5.close();

                        Map<String, Object> curProperties;

                        doc = database.getDocument(id);//not required
                        curProperties = doc.getProperties();
                        docProperties = new HashMap<String, Object>();
                        docProperties.putAll(curProperties);
                        docProperties.put("Iteration", (i + 1) * 5);
                        doc.putProperties(docProperties);
                    }

                    Map<String, Object> curProperties;
                    doc = database.getDocument(id);//not required
                    curProperties = doc.getProperties();
                    assertEquals(22, curProperties.size());
                    Map<String, Object> attachments = (Map<String, Object>) doc.getCurrentRevision().getProperty("_attachments");
                    assertNotNull(attachments);
                    assertEquals(100, attachments.size());
                } catch (Exception e) {
                    Log.e(Database.TAG, "Error deserializing properties from JSON", e);
                    return false;
                }

                return true;
            }

            });
            assertTrue("transaction with set attachments sequentially failed", success);
        }

    /**
     * Regression test for https://github.com/couchbase/couchbase-lite-android-core/issues/70
     */
    public void testAttachmentInstallBodies() throws Exception {

        Map<String, Object> attachmentsMap = new HashMap<String, Object>();
        Map<String, Object> attachmentMap = new HashMap<String, Object>();
        attachmentMap.put("length", 25);
        String attachmentName = "index.html";
        attachmentsMap.put(attachmentName, attachmentMap);

        Map<String, Object> updatedAttachments = Attachment.installAttachmentBodies(attachmentsMap, database);
        assertTrue(updatedAttachments.size() > 0);
        assertTrue(updatedAttachments.containsKey(attachmentName));


    }



}




Java Source Code List

com.couchbase.lite.ApiTest.java
com.couchbase.lite.AttachmentsTest.java
com.couchbase.lite.AuthTest.java
com.couchbase.lite.Base64Test.java
com.couchbase.lite.BlobStoreWriterTest.java
com.couchbase.lite.CRUDOperationsTest.java
com.couchbase.lite.CacheTest.java
com.couchbase.lite.ChangesTest.java
com.couchbase.lite.CollationTest.java
com.couchbase.lite.DatabaseTest.java
com.couchbase.lite.DocumentTest.java
com.couchbase.lite.LitePerfTestCase.java
com.couchbase.lite.LiteTestCase.java
com.couchbase.lite.LiteTestContext.java
com.couchbase.lite.LocalDocsTest.java
com.couchbase.lite.ManagerTest.java
com.couchbase.lite.MiscTest.java
com.couchbase.lite.MultipartReaderTest.java
com.couchbase.lite.RevTreeTest.java
com.couchbase.lite.RevisionsTest.java
com.couchbase.lite.RouterTest.java
com.couchbase.lite.SequenceMapTest.java
com.couchbase.lite.ValidationsTest.java
com.couchbase.lite.ViewsTest.java
com.couchbase.lite.android.AndroidContext.java
com.couchbase.lite.android.AndroidLogger.java
com.couchbase.lite.android.AndroidNetworkReachabilityManager.java
com.couchbase.lite.android.AndroidSQLiteStorageEngineFactory.java
com.couchbase.lite.android.AndroidSQLiteStorageEngine.java
com.couchbase.lite.mockserver.MockBulkDocs.java
com.couchbase.lite.mockserver.MockChangesFeedNoResponse.java
com.couchbase.lite.mockserver.MockChangesFeed.java
com.couchbase.lite.mockserver.MockCheckpointGet.java
com.couchbase.lite.mockserver.MockCheckpointPut.java
com.couchbase.lite.mockserver.MockDispatcher.java
com.couchbase.lite.mockserver.MockDocumentBulkGet.java
com.couchbase.lite.mockserver.MockDocumentGet.java
com.couchbase.lite.mockserver.MockDocumentPut.java
com.couchbase.lite.mockserver.MockFacebookAuthPost.java
com.couchbase.lite.mockserver.MockHelper.java
com.couchbase.lite.mockserver.MockPreloadedPullTarget.java
com.couchbase.lite.mockserver.MockRevsDiff.java
com.couchbase.lite.mockserver.MockSessionGet.java
com.couchbase.lite.mockserver.SmartMockResponse.java
com.couchbase.lite.mockserver.WrappedSmartMockResponse.java
com.couchbase.lite.performance2.Test01_CreateDocs.java
com.couchbase.lite.performance2.Test02_CreateDocsUnoptimizedWay.java
com.couchbase.lite.performance2.Test03_CreateDocsWithAttachments.java
com.couchbase.lite.performance2.Test06_PullReplication.java
com.couchbase.lite.performance2.Test07_PushReplication.java
com.couchbase.lite.performance2.Test08_DocRevisions.java
com.couchbase.lite.performance2.Test09_LoadDB.java
com.couchbase.lite.performance2.Test10_DeleteDB.java
com.couchbase.lite.performance2.Test11_DeleteDocs.java
com.couchbase.lite.performance2.Test12_IndexView.java
com.couchbase.lite.performance2.Test13_QueryView.java
com.couchbase.lite.performance2.Test14_ReduceView.java
com.couchbase.lite.performance2.Test28_KeySizes.java
com.couchbase.lite.performance2.Test29_AllDocQuery.java
com.couchbase.lite.performance2.Test30_LiveQuery.java
com.couchbase.lite.performance2.Test31_CompactDB.java
com.couchbase.lite.performance.Test10_DeleteDB.java
com.couchbase.lite.performance.Test11_DeleteDocs.java
com.couchbase.lite.performance.Test12_IndexView.java
com.couchbase.lite.performance.Test13_QueryView.java
com.couchbase.lite.performance.Test14_ReduceView.java
com.couchbase.lite.performance.Test16_ParallelPushReplication.java
com.couchbase.lite.performance.Test1_CreateDocs.java
com.couchbase.lite.performance.Test2_CreateDocsUnoptimizedWay.java
com.couchbase.lite.performance.Test3_CreateDocsWithAttachments.java
com.couchbase.lite.performance.Test6_PushReplication.java
com.couchbase.lite.performance.Test7_PullReplication.java
com.couchbase.lite.performance.Test8_DocRevisions.java
com.couchbase.lite.performance.Test9_LoadDB.java
com.couchbase.lite.replicator.BulkDownloaderTest.java
com.couchbase.lite.replicator.ChangeTrackerTest.java
com.couchbase.lite.replicator.CustomizableMockHttpClient.java
com.couchbase.lite.replicator.ReplicationTest.java
com.couchbase.lite.replicator.ResponderChain.java
com.couchbase.lite.support.BatcherTest.java
com.couchbase.lite.support.JsonDocumentTest.java
com.couchbase.lite.support.PersistentCookieStoreTest.java
com.couchbase.lite.support.RemoteRequestTest.java
com.couchbase.touchdb.RevCollator.java
com.couchbase.touchdb.TDCollateJSON.java