| // 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. |
| |
| couchTests.replication = function(debug) { |
| |
| if (debug) debugger; |
| |
| var host = CouchDB.host; |
| var sourceDb = new CouchDB("test_suite_db_a",{"X-Couch-Full-Commit":"false"}); |
| var targetDb = new CouchDB("test_suite_db_b",{"X-Couch-Full-Commit":"false"}); |
| |
| var dbPairs = [ |
| { |
| source: sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + host + "/" + sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: sourceDb.name, |
| target: CouchDB.protocol + host + "/" + targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + host + "/" + sourceDb.name, |
| target: CouchDB.protocol + host + "/" + targetDb.name |
| } |
| ]; |
| |
| var att1_data = CouchDB.request("GET", "/_utils/script/test/lorem.txt"); |
| att1_data = att1_data.responseText; |
| |
| var att2_data = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt"); |
| att2_data = att2_data.responseText; |
| |
| var sourceInfo, targetInfo; |
| var docs, doc, copy; |
| var repResult; |
| var i, j, k; |
| |
| |
| function makeAttData(minSize) { |
| var data = att1_data; |
| |
| while (data.length < minSize) { |
| data = data + att1_data; |
| } |
| return data; |
| } |
| |
| |
| function enableAttCompression(level, types) { |
| var xhr = CouchDB.request( |
| "PUT", |
| "/_config/attachments/compression_level", |
| { |
| body: JSON.stringify(level), |
| headers: {"X-Couch-Persist": "false"} |
| } |
| ); |
| T(xhr.status === 200); |
| xhr = CouchDB.request( |
| "PUT", |
| "/_config/attachments/compressible_types", |
| { |
| body: JSON.stringify(types), |
| headers: {"X-Couch-Persist": "false"} |
| } |
| ); |
| T(xhr.status === 200); |
| } |
| |
| |
| function disableAttCompression() { |
| var xhr = CouchDB.request( |
| "PUT", |
| "/_config/attachments/compression_level", |
| { |
| body: JSON.stringify("0"), |
| headers: {"X-Couch-Persist": "false"} |
| } |
| ); |
| T(xhr.status === 200); |
| } |
| |
| |
| function populateDb(db, docs, dontRecreateDb) { |
| if (dontRecreateDb !== true) { |
| db.deleteDb(); |
| wait(100); |
| db.createDb(); |
| } |
| for (var i = 0; i < docs.length; i++) { |
| var doc = docs[i]; |
| delete doc._rev; |
| } |
| if (docs.length > 0) { |
| db.bulkSave(docs); |
| } |
| } |
| |
| |
| function addAtt(db, doc, attName, attData, type) { |
| var uri = "/" + db.name + "/" + encodeURIComponent(doc._id) + "/" + attName; |
| |
| if (doc._rev) { |
| uri += "?rev=" + doc._rev; |
| } |
| |
| var xhr = CouchDB.request("PUT", uri, { |
| headers: { |
| "Content-Type": type |
| }, |
| body: attData |
| }); |
| |
| T(xhr.status === 201); |
| doc._rev = JSON.parse(xhr.responseText).rev; |
| } |
| |
| |
| function compareObjects(o1, o2) { |
| for (var p in o1) { |
| if (o1[p] === null && o2[p] !== null) { |
| return false; |
| } else if (typeof o1[p] === "object") { |
| if ((typeof o2[p] !== "object") || o2[p] === null) { |
| return false; |
| } |
| if (!arguments.callee(o1[p], o2[p])) { |
| return false; |
| } |
| } else { |
| if (o1[p] !== o2[p]) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| |
| function waitForSeq(sourceDb, targetDb) { |
| var targetSeq, |
| sourceSeq = sourceDb.info().update_seq, |
| t0 = new Date(), |
| t1, |
| ms = 3000; |
| |
| do { |
| targetSeq = targetDb.info().update_seq; |
| t1 = new Date(); |
| } while (((t1 - t0) <= ms) && targetSeq < sourceSeq); |
| } |
| |
| |
| function wait(ms) { |
| var t0 = new Date(), t1; |
| do { |
| CouchDB.request("GET", "/"); |
| t1 = new Date(); |
| } while ((t1 - t0) <= ms); |
| } |
| |
| |
| // test simple replications (not continuous, not filtered), including |
| // conflict creation |
| docs = makeDocs(1, 21); |
| docs.push({ |
| _id: "_design/foo", |
| language: "javascript", |
| value: "ddoc" |
| }); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| // add some attachments |
| for (j = 10; j < 15; j++) { |
| addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain"); |
| } |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| TEquals('string', typeof repResult.session_id); |
| TEquals(repResult.source_last_seq, sourceInfo.update_seq); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| TEquals(repResult.history[0].session_id, repResult.session_id); |
| TEquals('string', typeof repResult.history[0].start_time); |
| TEquals('string', typeof repResult.history[0].end_time); |
| TEquals(0, repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(sourceInfo.doc_count, repResult.history[0].missing_checked); |
| TEquals(sourceInfo.doc_count, repResult.history[0].missing_found); |
| TEquals(sourceInfo.doc_count, repResult.history[0].docs_read); |
| TEquals(sourceInfo.doc_count, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| |
| if (j >= 10 && j < 15) { |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(2, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att_copy.length); |
| TEquals(att1_data, att_copy); |
| } |
| } |
| |
| |
| // add one more doc to source, more attachments to some existing docs |
| // and replicate again |
| var newDoc = { |
| _id: "foo666", |
| value: "d" |
| }; |
| TEquals(true, sourceDb.save(newDoc).ok); |
| |
| // add some more attachments |
| for (j = 10; j < 15; j++) { |
| addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary"); |
| } |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(targetInfo.doc_count, sourceInfo.doc_count); |
| |
| TEquals('string', typeof repResult.session_id); |
| TEquals(sourceInfo.update_seq, repResult.source_last_seq); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(2, repResult.history.length); |
| TEquals(repResult.history[0].session_id, repResult.session_id); |
| TEquals('string', typeof repResult.history[0].start_time); |
| TEquals('string', typeof repResult.history[0].end_time); |
| TEquals((sourceInfo.update_seq - 6), repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(6, repResult.history[0].missing_checked); |
| TEquals(6, repResult.history[0].missing_found); |
| TEquals(6, repResult.history[0].docs_read); |
| TEquals(6, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(newDoc._id); |
| T(copy !== null); |
| TEquals(newDoc._id, copy._id); |
| TEquals(newDoc.value, copy.value); |
| |
| for (j = 10; j < 15; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(2, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att1_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att1_copy.length); |
| TEquals(att1_data, att1_copy); |
| |
| TEquals('object', typeof atts["data.dat"]); |
| TEquals(3, atts["data.dat"].revpos); |
| TEquals(0, atts["data.dat"].content_type.indexOf("application/binary")); |
| TEquals(true, atts["data.dat"].stub); |
| |
| var att2_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat" |
| ).responseText; |
| TEquals(att2_data.length, att2_copy.length); |
| TEquals(att2_data, att2_copy); |
| } |
| |
| // test deletion is replicated |
| doc = sourceDb.open(docs[1]._id); |
| TEquals(true, sourceDb.deleteDoc(doc).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(targetInfo.doc_count, sourceInfo.doc_count); |
| TEquals(targetInfo.doc_del_count, sourceInfo.doc_del_count); |
| TEquals(1, targetInfo.doc_del_count); |
| |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(3, repResult.history.length); |
| TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(1, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(docs[1]._id); |
| TEquals(null, copy); |
| |
| var changes = targetDb.changes({since: 0}); |
| var idx = changes.results.length - 1; |
| TEquals(docs[1]._id, changes.results[idx].id); |
| TEquals(true, changes.results[idx].deleted); |
| |
| // test conflict |
| doc = sourceDb.open(docs[0]._id); |
| doc.value = "white"; |
| TEquals(true, sourceDb.save(doc).ok); |
| |
| copy = targetDb.open(docs[0]._id); |
| copy.value = "black"; |
| TEquals(true, targetDb.save(copy).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(4, repResult.history.length); |
| TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(1, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(docs[0]._id, {conflicts: true}); |
| |
| TEquals(0, copy._rev.indexOf("2-")); |
| TEquals(true, copy._conflicts instanceof Array); |
| TEquals(1, copy._conflicts.length); |
| TEquals(0, copy._conflicts[0].indexOf("2-")); |
| |
| // replicate again with conflict |
| doc.value = "yellow"; |
| TEquals(true, sourceDb.save(doc).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(5, repResult.history.length); |
| TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(1, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(docs[0]._id, {conflicts: true}); |
| |
| TEquals(0, copy._rev.indexOf("3-")); |
| TEquals(true, copy._conflicts instanceof Array); |
| TEquals(1, copy._conflicts.length); |
| TEquals(0, copy._conflicts[0].indexOf("2-")); |
| |
| // resolve the conflict |
| TEquals(true, targetDb.deleteDoc({_id: copy._id, _rev: copy._conflicts[0]}).ok); |
| |
| // replicate again, check there are no more conflicts |
| doc.value = "rainbow"; |
| TEquals(true, sourceDb.save(doc).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(6, repResult.history.length); |
| TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(1, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(docs[0]._id, {conflicts: true}); |
| |
| TEquals(0, copy._rev.indexOf("4-")); |
| TEquals('undefined', typeof copy._conflicts); |
| |
| // test that revisions already in a target are not copied |
| TEquals(true, sourceDb.save({_id: "foo1", value: 111}).ok); |
| TEquals(true, targetDb.save({_id: "foo1", value: 111}).ok); |
| TEquals(true, sourceDb.save({_id: "foo2", value: 222}).ok); |
| TEquals(true, sourceDb.save({_id: "foo3", value: 333}).ok); |
| TEquals(true, targetDb.save({_id: "foo3", value: 333}).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| TEquals(sourceInfo.update_seq, repResult.source_last_seq); |
| TEquals(sourceInfo.update_seq - 3, repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(3, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| TEquals(true, sourceDb.save({_id: "foo4", value: 444}).ok); |
| TEquals(true, targetDb.save({_id: "foo4", value: 444}).ok); |
| TEquals(true, sourceDb.save({_id: "foo5", value: 555}).ok); |
| TEquals(true, targetDb.save({_id: "foo5", value: 555}).ok); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| TEquals(sourceInfo.update_seq, repResult.source_last_seq); |
| TEquals(sourceInfo.update_seq - 2, repResult.history[0].start_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq); |
| TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq); |
| TEquals(2, repResult.history[0].missing_checked); |
| TEquals(0, repResult.history[0].missing_found); |
| TEquals(0, repResult.history[0].docs_read); |
| TEquals(0, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.no_changes); |
| sourceInfo = sourceDb.info(); |
| TEquals(sourceInfo.update_seq, repResult.source_last_seq); |
| } |
| |
| |
| // test error when source database does not exist |
| try { |
| CouchDB.replicate("foobar", "test_suite_db"); |
| T(false, "should have failed with db_not_found error"); |
| } catch (x) { |
| TEquals("db_not_found", x.error); |
| } |
| |
| // validate COUCHDB-317 |
| try { |
| CouchDB.replicate("/foobar", "test_suite_db"); |
| T(false, "should have failed with db_not_found error"); |
| } catch (x) { |
| TEquals("db_not_found", x.error); |
| } |
| |
| try { |
| CouchDB.replicate(CouchDB.protocol + host + "/foobar", "test_suite_db"); |
| T(false, "should have failed with db_not_found error"); |
| } catch (x) { |
| TEquals("db_not_found", x.error); |
| } |
| |
| |
| // test since_seq parameter |
| docs = makeDocs(1, 6); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| var since_seq = 3; |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| var expected_ids = []; |
| var changes = sourceDb.changes({since: since_seq}); |
| for (j = 0; j < changes.results.length; j++) { |
| expected_ids.push(changes.results[j].id); |
| } |
| TEquals(2, expected_ids.length, "2 documents since since_seq"); |
| |
| // For OTP < R14B03, temporary child specs are kept in the supervisor |
| // after the child terminates, so cancel the replication to delete the |
| // child spec in those OTP releases, otherwise since_seq will have no |
| // effect. |
| try { |
| CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| {body: {cancel: true}} |
| ); |
| } catch (x) { |
| // OTP R14B03 onwards |
| TEquals("not found", x.error); |
| } |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| {body: {since_seq: since_seq}} |
| ); |
| // Same reason as before. But here we don't want since_seq to affect |
| // subsequent replications, so we need to delete the child spec from the |
| // supervisor (since_seq is not used to calculate the replication ID). |
| try { |
| CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| {body: {cancel: true}} |
| ); |
| } catch (x) { |
| // OTP R14B03 onwards |
| TEquals("not found", x.error); |
| } |
| TEquals(true, repResult.ok); |
| TEquals(2, repResult.history[0].missing_checked); |
| TEquals(2, repResult.history[0].missing_found); |
| TEquals(2, repResult.history[0].docs_read); |
| TEquals(2, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| if (expected_ids.indexOf(doc._id) === -1) { |
| T(copy === null); |
| } else { |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } |
| } |
| } |
| |
| |
| // test errors due to doc validate_doc_update functions in the target endpoint |
| docs = makeDocs(1, 8); |
| docs[2]["_attachments"] = { |
| "hello.txt": { |
| "content_type": "text/plain", |
| "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world") |
| } |
| }; |
| var ddoc = { |
| _id: "_design/test", |
| language: "javascript", |
| validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) { |
| if ((newDoc.integer % 2) !== 0) { |
| throw {forbidden: "I only like multiples of 2."}; |
| } |
| }).toString() |
| }; |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, [ddoc]); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target |
| ); |
| TEquals(true, repResult.ok); |
| TEquals(7, repResult.history[0].missing_checked); |
| TEquals(7, repResult.history[0].missing_found); |
| TEquals(7, repResult.history[0].docs_read); |
| TEquals(3, repResult.history[0].docs_written); |
| TEquals(4, repResult.history[0].doc_write_failures); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| if (doc.integer % 2 === 0) { |
| T(copy !== null); |
| TEquals(copy.integer, doc.integer); |
| } else { |
| T(copy === null); |
| } |
| } |
| } |
| |
| |
| // test create_target option |
| docs = makeDocs(1, 2); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, docs); |
| targetDb.deleteDb(); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| {body: {create_target: true}} |
| ); |
| TEquals(true, repResult.ok); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| TEquals(sourceInfo.update_seq, targetInfo.update_seq); |
| } |
| |
| |
| // test filtered replication |
| docs = makeDocs(1, 31); |
| docs.push({ |
| _id: "_design/mydesign", |
| language: "javascript", |
| filters: { |
| myfilter: (function(doc, req) { |
| var modulus = Number(req.query.modulus); |
| var special = req.query.special; |
| return (doc.integer % modulus === 0) || (doc.string === special); |
| }).toString() |
| } |
| }); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| filter: "mydesign/myfilter", |
| query_params: { |
| modulus: "2", |
| special: "7" |
| } |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| if ((doc.integer && (doc.integer % 2 === 0)) || (doc.string === "7")) { |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } else { |
| TEquals(null, copy); |
| } |
| } |
| |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| // NOT 31 (31 is db seq for last doc - the ddoc, which was not replicated) |
| TEquals(30, repResult.source_last_seq); |
| TEquals(0, repResult.history[0].start_last_seq); |
| TEquals(30, repResult.history[0].end_last_seq); |
| TEquals(30, repResult.history[0].recorded_seq); |
| // 16 => 15 docs with even integer field + 1 doc with string field "7" |
| TEquals(16, repResult.history[0].missing_checked); |
| TEquals(16, repResult.history[0].missing_found); |
| TEquals(16, repResult.history[0].docs_read); |
| TEquals(16, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| |
| // add new docs to source and resume the same replication |
| var newDocs = makeDocs(50, 56); |
| populateDb(sourceDb, newDocs, true); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| filter: "mydesign/myfilter", |
| query_params: { |
| modulus: "2", |
| special: "7" |
| } |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| |
| for (j = 0; j < newDocs.length; j++) { |
| doc = newDocs[j]; |
| copy = targetDb.open(doc._id); |
| |
| if (doc.integer && (doc.integer % 2 === 0)) { |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } else { |
| TEquals(null, copy); |
| } |
| } |
| |
| // last doc has even integer field, so last replicated seq is 36 |
| TEquals(36, repResult.source_last_seq); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(2, repResult.history.length); |
| TEquals(30, repResult.history[0].start_last_seq); |
| TEquals(36, repResult.history[0].end_last_seq); |
| TEquals(36, repResult.history[0].recorded_seq); |
| TEquals(3, repResult.history[0].missing_checked); |
| TEquals(3, repResult.history[0].missing_found); |
| TEquals(3, repResult.history[0].docs_read); |
| TEquals(3, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| } |
| |
| |
| // test filtered replication works as expected after changing the filter's |
| // code (ticket COUCHDB-892) |
| var filterFun1 = (function(doc, req) { |
| if (doc.value < Number(req.query.maxvalue)) { |
| return true; |
| } else { |
| return false; |
| } |
| }).toString(); |
| |
| var filterFun2 = (function(doc, req) { |
| return true; |
| }).toString(); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(targetDb, []); |
| populateDb(sourceDb, []); |
| |
| TEquals(true, sourceDb.save({_id: "foo1", value: 1}).ok); |
| TEquals(true, sourceDb.save({_id: "foo2", value: 2}).ok); |
| TEquals(true, sourceDb.save({_id: "foo3", value: 3}).ok); |
| TEquals(true, sourceDb.save({_id: "foo4", value: 4}).ok); |
| |
| var ddoc = { |
| "_id": "_design/mydesign", |
| "language": "javascript", |
| "filters": { |
| "myfilter": filterFun1 |
| } |
| }; |
| |
| TEquals(true, sourceDb.save(ddoc).ok); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| filter: "mydesign/myfilter", |
| query_params: { |
| maxvalue: "3" |
| } |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| TEquals(2, repResult.history[0].docs_written); |
| TEquals(2, repResult.history[0].docs_read); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| var docFoo1 = targetDb.open("foo1"); |
| T(docFoo1 !== null); |
| TEquals(1, docFoo1.value); |
| |
| var docFoo2 = targetDb.open("foo2"); |
| T(docFoo2 !== null); |
| TEquals(2, docFoo2.value); |
| |
| var docFoo3 = targetDb.open("foo3"); |
| TEquals(null, docFoo3); |
| |
| var docFoo4 = targetDb.open("foo4"); |
| TEquals(null, docFoo4); |
| |
| // replication should start from scratch after the filter's code changed |
| |
| ddoc.filters.myfilter = filterFun2; |
| TEquals(true, sourceDb.save(ddoc).ok); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| filter: "mydesign/myfilter", |
| query_params : { |
| maxvalue: "3" |
| } |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| TEquals(3, repResult.history[0].docs_written); |
| TEquals(3, repResult.history[0].docs_read); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| docFoo1 = targetDb.open("foo1"); |
| T(docFoo1 !== null); |
| TEquals(1, docFoo1.value); |
| |
| docFoo2 = targetDb.open("foo2"); |
| T(docFoo2 !== null); |
| TEquals(2, docFoo2.value); |
| |
| docFoo3 = targetDb.open("foo3"); |
| T(docFoo3 !== null); |
| TEquals(3, docFoo3.value); |
| |
| docFoo4 = targetDb.open("foo4"); |
| T(docFoo4 !== null); |
| TEquals(4, docFoo4.value); |
| |
| T(targetDb.open("_design/mydesign") !== null); |
| } |
| |
| |
| // test replication by doc IDs |
| docs = makeDocs(1, 11); |
| docs.push({ |
| _id: "_design/foo", |
| language: "javascript", |
| integer: 1 |
| }); |
| |
| var target_doc_ids = [ |
| { initial: ["1", "2", "10"], after: [], conflict_id: "2" }, |
| { initial: ["1", "2"], after: ["7"], conflict_id: "1" }, |
| { initial: ["1", "foo_666", "10"], after: ["7"], conflict_id: "10" }, |
| { initial: ["_design/foo", "8"], after: ["foo_5"], conflict_id: "8" }, |
| { initial: ["_design%2Ffoo", "8"], after: ["foo_5"], conflict_id: "8" }, |
| { initial: [], after: ["foo_1000", "_design/foo", "1"], conflict_id: "1" } |
| ]; |
| var doc_ids, after_doc_ids; |
| var id, num_inexistent_docs, after_num_inexistent_docs; |
| var total, after_total; |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| |
| for (j = 0; j < target_doc_ids.length; j++) { |
| doc_ids = target_doc_ids[j].initial; |
| num_inexistent_docs = 0; |
| |
| for (k = 0; k < doc_ids.length; k++) { |
| id = doc_ids[k]; |
| if (id.indexOf("foo_") === 0) { |
| num_inexistent_docs += 1; |
| } |
| } |
| |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| doc_ids: doc_ids |
| } |
| } |
| ); |
| |
| total = doc_ids.length - num_inexistent_docs; |
| TEquals(true, repResult.ok); |
| if (total === 0) { |
| TEquals(true, repResult.no_changes); |
| } else { |
| TEquals('string', typeof repResult.start_time); |
| TEquals('string', typeof repResult.end_time); |
| TEquals(total, repResult.docs_read); |
| TEquals(total, repResult.docs_written); |
| TEquals(0, repResult.doc_write_failures); |
| } |
| |
| for (k = 0; k < doc_ids.length; k++) { |
| id = decodeURIComponent(doc_ids[k]); |
| doc = sourceDb.open(id); |
| copy = targetDb.open(id); |
| |
| if (id.indexOf("foo_") === 0) { |
| TEquals(null, doc); |
| TEquals(null, copy); |
| } else { |
| T(doc !== null); |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } |
| } |
| |
| // be absolutely sure that other docs were not replicated |
| for (k = 0; k < docs.length; k++) { |
| var base_id = docs[k]._id; |
| id = encodeURIComponent(base_id); |
| doc = targetDb.open(base_id); |
| |
| if ((doc_ids.indexOf(id) >= 0) || (doc_ids.indexOf(base_id) >= 0)) { |
| T(doc !== null); |
| } else { |
| TEquals(null, doc); |
| } |
| } |
| |
| targetInfo = targetDb.info(); |
| TEquals(total, targetInfo.doc_count); |
| |
| |
| // add more docs throught replication by doc IDs |
| after_doc_ids = target_doc_ids[j].after; |
| after_num_inexistent_docs = 0; |
| |
| for (k = 0; k < after_doc_ids.length; k++) { |
| id = after_doc_ids[k]; |
| if (id.indexOf("foo_") === 0) { |
| after_num_inexistent_docs += 1; |
| } |
| } |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| doc_ids: after_doc_ids |
| } |
| } |
| ); |
| |
| after_total = after_doc_ids.length - after_num_inexistent_docs; |
| TEquals(true, repResult.ok); |
| if (after_total === 0) { |
| TEquals(true, repResult.no_changes); |
| } else { |
| TEquals('string', typeof repResult.start_time); |
| TEquals('string', typeof repResult.end_time); |
| TEquals(after_total, repResult.docs_read); |
| TEquals(after_total, repResult.docs_written); |
| TEquals(0, repResult.doc_write_failures); |
| } |
| |
| for (k = 0; k < after_doc_ids.length; k++) { |
| id = after_doc_ids[k]; |
| doc = sourceDb.open(id); |
| copy = targetDb.open(id); |
| |
| if (id.indexOf("foo_") === 0) { |
| TEquals(null, doc); |
| TEquals(null, copy); |
| } else { |
| T(doc !== null); |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } |
| } |
| |
| // be absolutely sure that other docs were not replicated |
| for (k = 0; k < docs.length; k++) { |
| var base_id = docs[k]._id; |
| id = encodeURIComponent(base_id); |
| doc = targetDb.open(base_id); |
| |
| if ((doc_ids.indexOf(id) >= 0) || (after_doc_ids.indexOf(id) >= 0) || |
| (doc_ids.indexOf(base_id) >= 0) || |
| (after_doc_ids.indexOf(base_id) >= 0)) { |
| T(doc !== null); |
| } else { |
| TEquals(null, doc); |
| } |
| } |
| |
| targetInfo = targetDb.info(); |
| TEquals((total + after_total), targetInfo.doc_count); |
| |
| |
| // replicate again the same doc after updated on source (no conflict) |
| id = target_doc_ids[j].conflict_id; |
| doc = sourceDb.open(id); |
| T(doc !== null); |
| doc.integer = 666; |
| TEquals(true, sourceDb.save(doc).ok); |
| addAtt(sourceDb, doc, "readme.txt", att1_data, "text/plain"); |
| addAtt(sourceDb, doc, "data.dat", att2_data, "application/binary"); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| doc_ids: [id] |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| TEquals(1, repResult.docs_read); |
| TEquals(1, repResult.docs_written); |
| TEquals(0, repResult.doc_write_failures); |
| |
| copy = targetDb.open(id, {conflicts: true}); |
| |
| TEquals(666, copy.integer); |
| TEquals(0, copy._rev.indexOf("4-")); |
| TEquals('undefined', typeof copy._conflicts); |
| |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(3, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att1_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att1_copy.length); |
| TEquals(att1_data, att1_copy); |
| |
| TEquals('object', typeof atts["data.dat"]); |
| TEquals(4, atts["data.dat"].revpos); |
| TEquals(0, atts["data.dat"].content_type.indexOf("application/binary")); |
| TEquals(true, atts["data.dat"].stub); |
| |
| var att2_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat" |
| ).responseText; |
| TEquals(att2_data.length, att2_copy.length); |
| TEquals(att2_data, att2_copy); |
| |
| |
| // generate a conflict throught replication by doc IDs |
| id = target_doc_ids[j].conflict_id; |
| doc = sourceDb.open(id); |
| copy = targetDb.open(id); |
| T(doc !== null); |
| T(copy !== null); |
| doc.integer += 100; |
| copy.integer += 1; |
| TEquals(true, sourceDb.save(doc).ok); |
| TEquals(true, targetDb.save(copy).ok); |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| doc_ids: [id] |
| } |
| } |
| ); |
| |
| TEquals(true, repResult.ok); |
| TEquals(1, repResult.docs_read); |
| TEquals(1, repResult.docs_written); |
| TEquals(0, repResult.doc_write_failures); |
| |
| copy = targetDb.open(id, {conflicts: true}); |
| |
| TEquals(0, copy._rev.indexOf("5-")); |
| TEquals(true, copy._conflicts instanceof Array); |
| TEquals(1, copy._conflicts.length); |
| TEquals(0, copy._conflicts[0].indexOf("5-")); |
| } |
| } |
| |
| |
| docs = makeDocs(1, 25); |
| docs.push({ |
| _id: "_design/foo", |
| language: "javascript", |
| filters: { |
| myfilter: (function(doc, req) { return true; }).toString() |
| } |
| }); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| // add some attachments |
| for (j = 10; j < 15; j++) { |
| addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain"); |
| } |
| |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| continuous: true |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals('string', typeof repResult._local_id); |
| |
| var rep_id = repResult._local_id; |
| |
| waitForSeq(sourceDb, targetDb); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| |
| if (j >= 10 && j < 15) { |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(2, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att_copy.length); |
| TEquals(att1_data, att_copy); |
| } |
| } |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| // add attachments to docs in source |
| for (j = 10; j < 15; j++) { |
| addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary"); |
| } |
| |
| var ddoc = docs[docs.length - 1]; // design doc |
| addAtt(sourceDb, ddoc, "readme.txt", att1_data, "text/plain"); |
| |
| waitForSeq(sourceDb, targetDb); |
| |
| var modifDocs = docs.slice(10, 15).concat([ddoc]); |
| for (j = 0; j < modifDocs.length; j++) { |
| doc = modifDocs[j]; |
| copy = targetDb.open(doc._id); |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(2, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att1_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att1_copy.length); |
| TEquals(att1_data, att1_copy); |
| |
| if (doc._id.indexOf("_design/") === -1) { |
| TEquals('object', typeof atts["data.dat"]); |
| TEquals(3, atts["data.dat"].revpos); |
| TEquals(0, atts["data.dat"].content_type.indexOf("application/binary")); |
| TEquals(true, atts["data.dat"].stub); |
| |
| var att2_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat" |
| ).responseText; |
| TEquals(att2_data.length, att2_copy.length); |
| TEquals(att2_data, att2_copy); |
| } |
| } |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| // add another attachment to the ddoc on source |
| addAtt(sourceDb, ddoc, "data.dat", att2_data, "application/binary"); |
| |
| waitForSeq(sourceDb, targetDb); |
| |
| copy = targetDb.open(ddoc._id); |
| var atts = copy._attachments; |
| TEquals('object', typeof atts); |
| TEquals('object', typeof atts["readme.txt"]); |
| TEquals(2, atts["readme.txt"].revpos); |
| TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain")); |
| TEquals(true, atts["readme.txt"].stub); |
| |
| var att1_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt" |
| ).responseText; |
| TEquals(att1_data.length, att1_copy.length); |
| TEquals(att1_data, att1_copy); |
| |
| TEquals('object', typeof atts["data.dat"]); |
| TEquals(3, atts["data.dat"].revpos); |
| TEquals(0, atts["data.dat"].content_type.indexOf("application/binary")); |
| TEquals(true, atts["data.dat"].stub); |
| |
| var att2_copy = CouchDB.request( |
| "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat" |
| ).responseText; |
| TEquals(att2_data.length, att2_copy.length); |
| TEquals(att2_data, att2_copy); |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| |
| // add more docs to source |
| var newDocs = makeDocs(25, 35); |
| populateDb(sourceDb, newDocs, true); |
| |
| waitForSeq(sourceDb, targetDb); |
| |
| for (j = 0; j < newDocs.length; j++) { |
| doc = newDocs[j]; |
| copy = targetDb.open(doc._id); |
| |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } |
| |
| sourceInfo = sourceDb.info(); |
| targetInfo = targetDb.info(); |
| |
| TEquals(sourceInfo.doc_count, targetInfo.doc_count); |
| |
| // delete docs from source |
| TEquals(true, sourceDb.deleteDoc(newDocs[0]).ok); |
| wait(1000); |
| TEquals(true, sourceDb.deleteDoc(newDocs[6]).ok); |
| |
| waitForSeq(sourceDb, targetDb); |
| |
| copy = targetDb.open(newDocs[0]._id); |
| TEquals(null, copy); |
| copy = targetDb.open(newDocs[6]._id); |
| TEquals(null, copy); |
| |
| var changes = targetDb.changes({since: targetInfo.update_seq}); |
| var line1 = changes.results[changes.results.length - 2]; |
| var line2 = changes.results[changes.results.length - 1]; |
| TEquals(newDocs[0]._id, line1.id); |
| TEquals(true, line1.deleted); |
| TEquals(newDocs[6]._id, line2.id); |
| TEquals(true, line2.deleted); |
| |
| // cancel the replication |
| repResult = CouchDB.replicate( |
| dbPairs[i].source, |
| dbPairs[i].target, |
| { |
| body: { |
| continuous: true, |
| cancel: true |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals(rep_id, repResult._local_id); |
| |
| doc = { |
| _id: 'foobar', |
| value: 666 |
| }; |
| TEquals(true, sourceDb.save(doc).ok); |
| |
| wait(2000); |
| copy = targetDb.open(doc._id); |
| TEquals(null, copy); |
| } |
| |
| // COUCHDB-1093 - filtered and continuous _changes feed dies when the |
| // database is compacted |
| docs = makeDocs(1, 10); |
| docs.push({ |
| _id: "_design/foo", |
| language: "javascript", |
| filters: { |
| myfilter: (function(doc, req) { return true; }).toString() |
| } |
| }); |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| repResult = CouchDB.replicate( |
| CouchDB.protocol + host + "/" + sourceDb.name, |
| targetDb.name, |
| { |
| body: { |
| continuous: true, |
| filter: "foo/myfilter" |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals('string', typeof repResult._local_id); |
| |
| var xhr = CouchDB.request("GET", "/_active_tasks"); |
| var tasks = JSON.parse(xhr.responseText); |
| |
| TEquals(true, sourceDb.compact().ok); |
| while (sourceDb.info().compact_running) {}; |
| |
| TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok); |
| xhr = CouchDB.request("GET", "/_active_tasks"); |
| |
| var tasksAfter = JSON.parse(xhr.responseText); |
| TEquals(tasks.length, tasksAfter.length); |
| waitForSeq(sourceDb, targetDb); |
| T(sourceDb.open("30") !== null); |
| |
| // cancel replication |
| repResult = CouchDB.replicate( |
| CouchDB.protocol + host + "/" + sourceDb.name, |
| targetDb.name, |
| { |
| body: { |
| continuous: true, |
| filter: "foo/myfilter", |
| cancel: true |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals('string', typeof repResult._local_id); |
| |
| |
| // |
| // test replication of compressed attachments |
| // |
| doc = { |
| _id: "foobar" |
| }; |
| var bigTextAtt = makeAttData(128 * 1024); |
| var attName = "readme.txt"; |
| var xhr = CouchDB.request("GET", "/_config/attachments/compression_level"); |
| var compressionLevel = JSON.parse(xhr.responseText); |
| xhr = CouchDB.request("GET", "/_config/attachments/compressible_types"); |
| var compressibleTypes = JSON.parse(xhr.responseText); |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| populateDb(sourceDb, [doc]); |
| populateDb(targetDb, []); |
| |
| // enable compression of text types |
| enableAttCompression("8", "text/*"); |
| |
| // add text attachment to foobar doc |
| xhr = CouchDB.request( |
| "PUT", |
| "/" + sourceDb.name + "/" + doc._id + "/" + attName + "?rev=" + doc._rev, |
| { |
| body: bigTextAtt, |
| headers: {"Content-Type": "text/plain"} |
| } |
| ); |
| TEquals(201, xhr.status); |
| |
| // disable compression and replicate |
| disableAttCompression(); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| TEquals(1, repResult.history[0].missing_checked); |
| TEquals(1, repResult.history[0].missing_found); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open( |
| doc._id, |
| {att_encoding_info: true, bypass_cache: Math.round(Math.random() * 1000)} |
| ); |
| T(copy !== null); |
| T(attName in copy._attachments); |
| TEquals("gzip", copy._attachments[attName].encoding); |
| TEquals("number", typeof copy._attachments[attName].length); |
| TEquals("number", typeof copy._attachments[attName].encoded_length); |
| T(copy._attachments[attName].encoded_length < copy._attachments[attName].length); |
| } |
| |
| delete bigTextAtt; |
| // restore original settings |
| enableAttCompression(compressionLevel, compressibleTypes); |
| |
| |
| // |
| // test replication triggered by non admins |
| // |
| |
| // case 1) user triggering the replication is not a DB admin of the target DB |
| var joeUserDoc = CouchDB.prepareUserDoc({ |
| name: "joe", |
| roles: ["erlanger"] |
| }, "erly"); |
| var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"}); |
| var server_config = [ |
| { |
| section: "couch_httpd_auth", |
| key: "authentication_db", |
| value: usersDb.name |
| } |
| ]; |
| |
| docs = makeDocs(1, 6); |
| docs.push({ |
| _id: "_design/foo", |
| language: "javascript" |
| }); |
| |
| dbPairs = [ |
| { |
| source: sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + host + "/" + sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: sourceDb.name, |
| target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + host + "/" + sourceDb.name, |
| target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name |
| } |
| ]; |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| usersDb.deleteDb(); |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| TEquals(true, targetDb.setSecObj({ |
| admins: { |
| names: ["superman"], |
| roles: ["god"] |
| } |
| }).ok); |
| |
| run_on_modified_server(server_config, function() { |
| delete joeUserDoc._rev; |
| TEquals(true, usersDb.save(joeUserDoc).ok); |
| |
| TEquals(true, CouchDB.login("joe", "erly").ok); |
| TEquals('joe', CouchDB.session().userCtx.name); |
| |
| repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| |
| TEquals(true, CouchDB.logout().ok); |
| |
| TEquals(true, repResult.ok); |
| TEquals(docs.length, repResult.history[0].docs_read); |
| TEquals((docs.length - 1), repResult.history[0].docs_written); // 1 ddoc |
| TEquals(1, repResult.history[0].doc_write_failures); |
| }); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| |
| if (doc._id.indexOf("_design/") === 0) { |
| TEquals(null, copy); |
| } else { |
| T(copy !== null); |
| TEquals(true, compareObjects(doc, copy)); |
| } |
| } |
| } |
| |
| // case 2) user triggering the replication is not a reader (nor admin) of the |
| // source DB |
| dbPairs = [ |
| { |
| source: sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name, |
| target: targetDb.name |
| }, |
| { |
| source: sourceDb.name, |
| target: CouchDB.protocol + host + "/" + targetDb.name |
| }, |
| { |
| source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name, |
| target: CouchDB.protocol + host + "/" + targetDb.name |
| } |
| ]; |
| |
| for (i = 0; i < dbPairs.length; i++) { |
| usersDb.deleteDb(); |
| populateDb(sourceDb, docs); |
| populateDb(targetDb, []); |
| |
| TEquals(true, sourceDb.setSecObj({ |
| admins: { |
| names: ["superman"], |
| roles: ["god"] |
| }, |
| readers: { |
| names: ["john"], |
| roles: ["secret"] |
| } |
| }).ok); |
| |
| run_on_modified_server(server_config, function() { |
| delete joeUserDoc._rev; |
| TEquals(true, usersDb.save(joeUserDoc).ok); |
| |
| TEquals(true, CouchDB.login("joe", "erly").ok); |
| TEquals('joe', CouchDB.session().userCtx.name); |
| |
| try { |
| CouchDB.replicate(dbPairs[i].source, dbPairs[i].target); |
| T(false, "should have raised an exception"); |
| } catch (x) { |
| TEquals("unauthorized", x.error); |
| } |
| |
| TEquals(true, CouchDB.logout().ok); |
| }); |
| |
| for (j = 0; j < docs.length; j++) { |
| doc = docs[j]; |
| copy = targetDb.open(doc._id); |
| TEquals(null, copy); |
| } |
| } |
| |
| |
| // COUCHDB-885 - push replication of a doc with attachment causes a |
| // conflict in the target. |
| sourceDb = new CouchDB("test_suite_db_a"); |
| targetDb = new CouchDB("test_suite_db_b"); |
| |
| sourceDb.deleteDb(); |
| sourceDb.createDb(); |
| targetDb.deleteDb(); |
| targetDb.createDb(); |
| |
| doc = { |
| _id: "doc1" |
| }; |
| TEquals(true, sourceDb.save(doc).ok); |
| |
| repResult = CouchDB.replicate( |
| sourceDb.name, |
| CouchDB.protocol + host + "/" + targetDb.name |
| ); |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(1, repResult.history.length); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| doc["_attachments"] = { |
| "hello.txt": { |
| "content_type": "text/plain", |
| "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world") |
| }, |
| "foo.dat": { |
| "content_type": "not/compressible", |
| "data": "aSBhbSBub3QgZ3ppcGVk" // base64:encode("i am not gziped") |
| } |
| }; |
| |
| TEquals(true, sourceDb.save(doc).ok); |
| repResult = CouchDB.replicate( |
| sourceDb.name, |
| CouchDB.protocol + host + "/" + targetDb.name |
| ); |
| TEquals(true, repResult.ok); |
| TEquals(true, repResult.history instanceof Array); |
| TEquals(2, repResult.history.length); |
| TEquals(1, repResult.history[0].docs_written); |
| TEquals(1, repResult.history[0].docs_read); |
| TEquals(0, repResult.history[0].doc_write_failures); |
| |
| copy = targetDb.open(doc._id, { |
| conflicts: true, deleted_conflicts: true, |
| attachments: true, att_encoding_info: true}); |
| T(copy !== null); |
| TEquals("undefined", typeof copy._conflicts); |
| TEquals("undefined", typeof copy._deleted_conflicts); |
| TEquals("text/plain", copy._attachments["hello.txt"]["content_type"]); |
| TEquals("aGVsbG8gd29ybGQ=", copy._attachments["hello.txt"]["data"]); |
| TEquals("gzip", copy._attachments["hello.txt"]["encoding"]); |
| TEquals("not/compressible", copy._attachments["foo.dat"]["content_type"]); |
| TEquals("aSBhbSBub3QgZ3ppcGVk", copy._attachments["foo.dat"]["data"]); |
| TEquals("undefined", typeof copy._attachments["foo.dat"]["encoding"]); |
| // end of test for COUCHDB-885 |
| |
| // Test for COUCHDB-1242 (reject non-string query_params) |
| try { |
| CouchDB.replicate(sourceDb, targetDb, { |
| body: { |
| filter : "mydesign/myfilter", |
| query_params : { |
| "maxvalue": 4 |
| } |
| } |
| }); |
| } catch (e) { |
| TEquals("bad_request", e.error); |
| } |
| |
| |
| // Test that we can cancel a replication just by POSTing an object |
| // like {"replication_id": Id, "cancel": true}. The replication ID |
| // can be obtained from a continuous replication request response |
| // (_local_id field), from _active_tasks or from the log |
| populateDb(sourceDb, makeDocs(1, 6)); |
| populateDb(targetDb, []); |
| |
| repResult = CouchDB.replicate( |
| CouchDB.protocol + host + "/" + sourceDb.name, |
| targetDb.name, |
| { |
| body: { |
| continuous: true, |
| create_target: true |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals('string', typeof repResult._local_id); |
| |
| xhr = CouchDB.request("GET", "/_active_tasks"); |
| tasks = JSON.parse(xhr.responseText); |
| |
| var repId; |
| for (j = 0; j < tasks.length; j++) { |
| if (tasks[j].replication_id === repResult._local_id) { |
| repId = tasks[j].replication_id; |
| } |
| } |
| |
| TEquals(repResult._local_id, repId, "Replication found in _active_tasks"); |
| xhr = CouchDB.request( |
| "POST", "/_replicate", { |
| body: JSON.stringify({"replication_id": repId, "cancel": true}), |
| headers: {"Content-Type": "application/json"} |
| }); |
| TEquals(200, xhr.status, "Replication cancel request success"); |
| |
| xhr = CouchDB.request("GET", "/_active_tasks"); |
| tasks = JSON.parse(xhr.responseText); |
| repId = null; |
| for (j = 0; j < tasks.length; j++) { |
| if (tasks[j].replication_id === repResult._local_id) { |
| repId = tasks[j].replication_id; |
| } |
| } |
| TEquals(null, repId, "Replication was canceled"); |
| |
| xhr = CouchDB.request( |
| "POST", "/_replicate", { |
| body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}), |
| headers: {"Content-Type": "application/json"} |
| }); |
| TEquals(404, xhr.status, "2nd replication cancel failed"); |
| |
| // Non-admin user can not cancel replications triggered by other users |
| var userDoc = CouchDB.prepareUserDoc({ |
| name: "tony", |
| roles: ["mafia"] |
| }, "soprano"); |
| usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"}); |
| server_config = [ |
| { |
| section: "couch_httpd_auth", |
| key: "authentication_db", |
| value: usersDb.name |
| } |
| ]; |
| |
| run_on_modified_server(server_config, function() { |
| populateDb(sourceDb, makeDocs(1, 6)); |
| populateDb(targetDb, []); |
| TEquals(true, usersDb.save(userDoc).ok); |
| |
| repResult = CouchDB.replicate( |
| CouchDB.protocol + host + "/" + sourceDb.name, |
| targetDb.name, |
| { |
| body: { |
| continuous: true |
| } |
| } |
| ); |
| TEquals(true, repResult.ok); |
| TEquals('string', typeof repResult._local_id); |
| |
| TEquals(true, CouchDB.login("tony", "soprano").ok); |
| TEquals('tony', CouchDB.session().userCtx.name); |
| |
| xhr = CouchDB.request( |
| "POST", "/_replicate", { |
| body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}), |
| headers: {"Content-Type": "application/json"} |
| }); |
| TEquals(401, xhr.status, "Unauthorized to cancel replication"); |
| TEquals("unauthorized", JSON.parse(xhr.responseText).error); |
| |
| TEquals(true, CouchDB.logout().ok); |
| |
| xhr = CouchDB.request( |
| "POST", "/_replicate", { |
| body: JSON.stringify({"replication_id": repResult._local_id, "cancel": true}), |
| headers: {"Content-Type": "application/json"} |
| }); |
| TEquals(200, xhr.status, "Authorized to cancel replication"); |
| }); |
| |
| // cleanup |
| usersDb.deleteDb(); |
| sourceDb.deleteDb(); |
| targetDb.deleteDb(); |
| }; |