public void TestLoadRevisionBody()
        {
            var document = database.CreateDocument();
            var properties = new Dictionary<string, object>();
            properties["foo"] = "foo";
            properties["bar"] = false;
            properties["_id"] = document.Id;
            document.PutProperties(properties);
            properties.SetRevID(document.CurrentRevisionId);
            Assert.IsNotNull(document.CurrentRevision);

            var revisionInternal = new RevisionInternal(
                document.Id, document.CurrentRevisionId.AsRevID(), false);

            database.LoadRevisionBody(revisionInternal);
            Assert.AreEqual(properties, revisionInternal.GetProperties());
            revisionInternal.SetBody(null);

            // now lets purge the document, and then try to load the revision body again
            document.Purge();

            var gotExpectedException = false;
            try {
                database.LoadRevisionBody(revisionInternal);
            } catch (CouchbaseLiteException e) {
                gotExpectedException |= 
                    e.CBLStatus.Code == StatusCode.NotFound;
            }

            Assert.IsTrue(gotExpectedException);
        }
        public void TestHistoryAfterDocDeletion()
        {
            var properties = new Dictionary<string, object>() 
            {
                {"tag", 1}
            };

            var docId = "testHistoryAfterDocDeletion";
            var doc = database.GetDocument(docId);
            Assert.AreEqual(docId, doc.Id);
            doc.PutProperties(properties);

            var revId = doc.CurrentRevisionId.AsRevID();
            for (var i = 2; i < 6; i++)
            {
                properties["tag"] = i;
                properties.SetRevID(revId);
                doc.PutProperties(properties);
                revId = doc.CurrentRevisionId.AsRevID();
                Assert.AreEqual(i, revId.Generation);
                Assert.AreEqual(docId, doc.Id);
            }

            // now delete the doc and clear it from the cache so we
            // make sure we are reading a fresh copy
            doc.Delete();
            database.RemoveDocumentFromCache(doc);

            // get doc from db with same ID as before, and the current rev should be null since the
            // last update was a deletion
            var docPostDelete = database.GetDocument(docId);
            Assert.IsNull(docPostDelete.CurrentRevision);

            properties = new Dictionary<string, object>() 
            {
                { "tag", 6 }
            };

            var newRevision = docPostDelete.CreateRevision();
            newRevision.SetProperties(properties);
            var newSavedRevision = newRevision.Save();

            // make sure the current revision of doc matches the rev we just saved
            Assert.AreEqual(newSavedRevision, docPostDelete.CurrentRevision);

            // make sure the rev id is 7-
            Assert.IsTrue(docPostDelete.CurrentRevisionId.StartsWith("7-", StringComparison.Ordinal));
        }
        public void TestPutDeletedDocument() 
        {
            Document document = database.CreateDocument();
            var properties = new Dictionary<string, object>();
            properties["foo"] = "foo";
            properties["bar"] = false;
            document.PutProperties(properties);
            Assert.IsNotNull(document.CurrentRevision);

            var docId = document.Id;

            properties.SetRevID(document.CurrentRevisionId);
            properties["_deleted"] = true;
            properties["mykey"] = "myval";
            var newRev = document.PutProperties(properties);
            newRev.LoadProperties();

            Assert.IsTrue(newRev.Properties.ContainsKey("mykey"));
            Assert.IsTrue(document.Deleted);
            var featchedDoc = database.GetExistingDocument(docId);
            Assert.IsNull(featchedDoc);

            var queryAllDocs = database.CreateAllDocumentsQuery();
            var queryEnumerator = queryAllDocs.Run();
            foreach(QueryRow row in queryEnumerator)
            {
                Assert.AreNotEqual(row.Document.Id, docId);
            }
        }
        private void SaveLastSequence(SaveLastSequenceCompletionBlock completionHandler)
        {
            if (!lastSequenceChanged) {
                if (completionHandler != null) {
                    completionHandler();
                }
                return;
            }

            if (_savingCheckpoint) {
                // If a save is already in progress, don't do anything. (The completion block will trigger
                // another save after the first one finishes.)
                Task.Delay(500).ContinueWith(t => SaveLastSequence(completionHandler));
                return;
            }

            lastSequenceChanged = false;
            var lastSequence = LastSequence;
            Log.To.Sync.I(Tag, "{0} checkpointing sequence={1}", this, lastSequence);
            var body = new Dictionary<String, Object>();
            if (_remoteCheckpoint != null) {
                foreach (var pair in _remoteCheckpoint) {
                    body[pair.Key] = pair.Value;
                }
            }

            body["lastSequence"] = lastSequence;
            var remoteCheckpointDocID = RemoteCheckpointDocID();
            if (String.IsNullOrEmpty(remoteCheckpointDocID)) {
                Log.To.Sync.W(Tag, "remoteCheckpointDocID is null for {0}, aborting SaveLastSequence", this);
                if (completionHandler != null) { completionHandler(); }
                return;
            }

            _savingCheckpoint = true;
            var message = _remoteSession.SendAsyncRequest(HttpMethod.Put, "/_local/" + remoteCheckpointDocID, body, (result, e) => 
            {
                _savingCheckpoint = false;
                if (e != null) {
                    switch (GetStatusFromError(e)) {
                        case StatusCode.NotFound:
                            Log.To.Sync.I(Tag, "Got 404 from _local, ignoring...");
                            _remoteCheckpoint = null;
                            break;
                        case StatusCode.Conflict:
                            Log.To.Sync.I(Tag, "Got 409 from _local, retrying...");
                            RefreshRemoteCheckpointDoc();
                            break;
                        default:
                            Log.To.Sync.W(Tag, String.Format("Unable to save remote checkpoint for {0}", _replicatorID), e);
                            // TODO: On 401 or 403, and this is a pull, remember that remote
                            // TODO: is read-only & don't attempt to read its checkpoint next time.
                            break;
                    }
                } else {
                    var response = result.AsDictionary<string, object>();
                    var rev = response.GetCast<string>("rev");
                    if(rev != null) {
                        body.SetRevID(rev);
                    }

                    _remoteCheckpoint = body;
                    var localDb = LocalDatabase;
                    if(localDb.Storage == null) {
                        Log.To.Sync.I(Tag, "Database is null or closed, ignoring remote checkpoint response");
                        if(completionHandler != null) {
                            completionHandler();
                        }
                        return;
                    }

                    localDb.SetLastSequence(LastSequence, remoteCheckpointDocID);
                    Log.To.Sync.I(Tag, "{0} saved remote checkpoint '{1}' (_rev={2})", this, lastSequence, rev);
                }

                if (completionHandler != null) {
                    completionHandler ();
                }
            }, true);

            // This request should not be canceled when the replication is told to stop:
            if(message != null) {
                Task dummy;
                _remoteSession?._requests.TryRemove(message, out dummy);
            }
        }