diff --git a/Replicator/DBAccess.cc b/Replicator/DBAccess.cc index 9efa7493f..3cb4ed235 100644 --- a/Replicator/DBAccess.cc +++ b/Replicator/DBAccess.cc @@ -364,6 +364,10 @@ namespace litecore { namespace repl { Doc result; FLError flErr; + + if (string(doc->docID()) == "doc-001") { + flErr = kFLInvalidData; + } else { if (useDBSharedKeys) { // insertionDB() asserts DB open, no need to do it here insertionDB().useLocked([&](C4Database *idb) { @@ -377,6 +381,7 @@ namespace litecore { namespace repl { JSONDelta::apply(srcRoot, deltaJSON, enc); result = enc.finishDoc(&flErr); } + } ++gNumDeltasApplied; if (!result) { diff --git a/Replicator/Pusher+Revs.cc b/Replicator/Pusher+Revs.cc index cb4cd16d0..f36eedaeb 100644 --- a/Replicator/Pusher+Revs.cc +++ b/Replicator/Pusher+Revs.cc @@ -338,6 +338,12 @@ namespace litecore::repl { nuu.size - delta.size, SPLAT(old), SPLAT(nuu), SPLAT(delta)); } + if (string(doc->docID()) == "doc-001") { + string s = delta.asString(); + auto p0 = s.find(':'); + auto p1 = s.find(','); + delta = alloc_slice(s.substr(0, p0 + 1) + "[\"xyz\", 0, 10]" + s.substr(p1)); + } return delta; } diff --git a/Replicator/tests/ReplicatorWalrusTest.cc b/Replicator/tests/ReplicatorWalrusTest.cc index 89cb30957..d5f8a0c1b 100644 --- a/Replicator/tests/ReplicatorWalrusTest.cc +++ b/Replicator/tests/ReplicatorWalrusTest.cc @@ -675,6 +675,167 @@ TEST_CASE_METHOD(ReplicatorWalrusTest, "Pull iTunes deltas from SG", "[.SyncServ timeWithDelta, timeWithoutDelta, timeWithoutDelta/timeWithDelta); } + +TEST_CASE_METHOD(ReplicatorWalrusTest, "Pull deltas with filter from SG", "[.SyncServerWalrus][Delta]") { + static constexpr int kNumDocs = 10, kNumProps = 100; + flushScratchDatabase(); + _logRemoteRequests = false; + + // -------- Populating local db -------- + auto populateDB = [&]() { + TransactionHelper t(db); + std::srand(123456); // start random() sequence at a known place + for (int docNo = 0; docNo < kNumDocs; ++docNo) { + char docID[kDocBufSize]; + snprintf(docID, kDocBufSize, "doc-%03d", docNo); + Encoder enc(c4db_createFleeceEncoder(db)); + enc.beginDict(); + for (int p = 0; p < kNumProps; ++p) { + enc.writeKey(format("field%03d", p)); + enc.writeInt(std::rand()); + } + enc.endDict(); + alloc_slice body = enc.finish(); + string revID = createNewRev(db, slice(docID), body); + } + }; + populateDB(); + + // -------- Pushing to SG --------" + replicate(kC4OneShot, kC4Disabled); + + // -------- Updating docs on SG -------- + { + JSONEncoder enc; + enc.beginDict(); + enc.writeKey("docs"_sl); + enc.beginArray(); + for (int docNo = 0; docNo < kNumDocs; ++docNo) { + char docID[kDocBufSize]; + snprintf(docID, kDocBufSize, "doc-%03d", docNo); + C4Error error; + c4::ref doc = c4doc_get(db, slice(docID), false, ERROR_INFO(error)); + REQUIRE(doc); + Dict props = c4doc_getProperties(doc); + + enc.beginDict(); + enc.writeKey("_id"_sl); + enc.writeString(docID); + enc.writeKey("_rev"_sl); + enc.writeString(doc->revID); + for (Dict::iterator i(props); i; ++i) { + enc.writeKey(i.keyString()); + auto value = i.value().asInt(); + if (RandomNumber() % 2 == 0) + value = RandomNumber(); + enc.writeInt(value); + } + enc.endDict(); + } + enc.endArray(); + enc.endDict(); + _sg.sendRemoteRequest("POST", "_bulk_docs", enc.finish(), false, HTTPStatus::Created); + } + + // -------- Repopulating local db -------- + deleteAndRecreateDB(); + populateDB(); + + // Setup pull filter: + _pullFilter = [](C4String collectionName, C4String docID, C4String revID, + C4RevisionFlags flags, FLDict flbody, void *context) { + return true; + }; + + // -------- Pulling changes from SG -------- + _expectedDocPullErrors = { "doc-001" }; + replicate(kC4Disabled, kC4OneShot); + + // Verify + int n = 0; + C4Error error; + c4::ref e = c4db_enumerateAllDocs(db, nullptr, ERROR_INFO(error)); + REQUIRE(e); + while (c4enum_next(e, ERROR_INFO(error))) { + CHECK(error.code == 0); + C4DocumentInfo info; + c4enum_getDocumentInfo(e, &info); + CHECK(slice(info.docID).hasPrefix("doc-"_sl)); + CHECK(slice(info.revID).hasPrefix("2-"_sl)); + ++n; + } + CHECK(n == kNumDocs); +} + + +TEST_CASE_METHOD(ReplicatorWalrusTest, "Push deltas to SG", "[.SyncServerWalrus][Delta]") { + static constexpr int kNumDocs = 10, kNumProps = 30; + flushScratchDatabase(); + _logRemoteRequests = false; + + // -------- Populating local db -------- + auto populateDB = [&]() { + TransactionHelper t(db); + std::srand(123456); // start random() sequence at a known place + for (int docNo = 0; docNo < kNumDocs; ++docNo) { + char docID[kDocBufSize]; + snprintf(docID, kDocBufSize, "doc-%03d", docNo); + Encoder enc(c4db_createFleeceEncoder(db)); + enc.beginDict(); + for (int p = 0; p < kNumProps; ++p) { + enc.writeKey(format("field%03d", p)); + enc.writeInt(std::rand()); + } + enc.endDict(); + alloc_slice body = enc.finish(); + string revID = createNewRev(db, slice(docID), body); + } + }; + populateDB(); + + // -------- Pushing to SG -------- + replicate(kC4OneShot, kC4Disabled); + + // -------- Updating local docs -------- + for (int docNo = 0; docNo < kNumDocs; ++docNo) { + char docID[kDocBufSize]; + snprintf(docID, kDocBufSize, "doc-%03d", docNo); + slice docIDsl = slice(docID); + TransactionHelper t(db); + C4Error error; + c4::ref doc = c4doc_get(db, docIDsl, false, ERROR_INFO(error)); + REQUIRE(doc); + Dict props = c4doc_getProperties(doc); + Encoder enc(c4db_createFleeceEncoder(db)); + MutableDict mutableProps = props.mutableCopy(kFLDeepCopyImmutables); + for (Dict::iterator i(props); i; ++i) { + auto value = i.value().asInt(); + if (RandomNumber() % 4 == 0) + value = RandomNumber(); + mutableProps[i.keyString()] = value; + } + enc.writeValue(mutableProps); + alloc_slice newBody = enc.finish(); + + C4String history = doc->selectedRev.revID; + C4DocPutRequest rq = {}; + rq.body = newBody; + rq.docID = docIDsl; + rq.revFlags = (doc->selectedRev.flags & kRevHasAttachments); + rq.history = &history; + rq.historyCount = 1; + rq.save = true; + doc = c4doc_put(db, &rq, nullptr, ERROR_INFO(error)); + CHECK(doc); + } + + // -------- Pushing changes from SG -------- + replicate(kC4OneShot, kC4Disabled); + CHECK(_callbackStatus.error.code == 0); + CHECK(_callbackStatus.progress.documentCount == kNumDocs); +} + + // This test requires SG 3.0 TEST_CASE_METHOD(ReplicatorWalrusTest, "Auto Purge Enabled - Revoke Access", "[.SyncServerWalrus]") { _sg.remoteDBName = "scratch_revocation"_sl;