PageRenderTime 57ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/share/www/script/test/replication.js

http://github.com/apache/couchdb
JavaScript | 1780 lines | 1398 code | 300 blank | 82 comment | 96 complexity | c0fa0d4e0713c6d945d6b4e008793169 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. couchTests.replication = function(debug) {
  13. if (debug) debugger;
  14. var host = CouchDB.host;
  15. var sourceDb = new CouchDB("test_suite_db_a",{"X-Couch-Full-Commit":"false"});
  16. var targetDb = new CouchDB("test_suite_db_b",{"X-Couch-Full-Commit":"false"});
  17. var dbPairs = [
  18. {
  19. source: sourceDb.name,
  20. target: targetDb.name
  21. },
  22. {
  23. source: CouchDB.protocol + host + "/" + sourceDb.name,
  24. target: targetDb.name
  25. },
  26. {
  27. source: sourceDb.name,
  28. target: CouchDB.protocol + host + "/" + targetDb.name
  29. },
  30. {
  31. source: CouchDB.protocol + host + "/" + sourceDb.name,
  32. target: CouchDB.protocol + host + "/" + targetDb.name
  33. }
  34. ];
  35. var att1_data = CouchDB.request("GET", "/_utils/script/test/lorem.txt");
  36. att1_data = att1_data.responseText;
  37. var att2_data = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt");
  38. att2_data = att2_data.responseText;
  39. var sourceInfo, targetInfo;
  40. var docs, doc, copy;
  41. var repResult;
  42. var i, j, k;
  43. function makeAttData(minSize) {
  44. var data = att1_data;
  45. while (data.length < minSize) {
  46. data = data + att1_data;
  47. }
  48. return data;
  49. }
  50. function enableAttCompression(level, types) {
  51. var xhr = CouchDB.request(
  52. "PUT",
  53. "/_config/attachments/compression_level",
  54. {
  55. body: JSON.stringify(level),
  56. headers: {"X-Couch-Persist": "false"}
  57. }
  58. );
  59. T(xhr.status === 200);
  60. xhr = CouchDB.request(
  61. "PUT",
  62. "/_config/attachments/compressible_types",
  63. {
  64. body: JSON.stringify(types),
  65. headers: {"X-Couch-Persist": "false"}
  66. }
  67. );
  68. T(xhr.status === 200);
  69. }
  70. function disableAttCompression() {
  71. var xhr = CouchDB.request(
  72. "PUT",
  73. "/_config/attachments/compression_level",
  74. {
  75. body: JSON.stringify("0"),
  76. headers: {"X-Couch-Persist": "false"}
  77. }
  78. );
  79. T(xhr.status === 200);
  80. }
  81. function populateDb(db, docs, dontRecreateDb) {
  82. if (dontRecreateDb !== true) {
  83. db.deleteDb();
  84. wait(100);
  85. db.createDb();
  86. }
  87. for (var i = 0; i < docs.length; i++) {
  88. var doc = docs[i];
  89. delete doc._rev;
  90. }
  91. if (docs.length > 0) {
  92. db.bulkSave(docs);
  93. }
  94. }
  95. function addAtt(db, doc, attName, attData, type) {
  96. var uri = "/" + db.name + "/" + encodeURIComponent(doc._id) + "/" + attName;
  97. if (doc._rev) {
  98. uri += "?rev=" + doc._rev;
  99. }
  100. var xhr = CouchDB.request("PUT", uri, {
  101. headers: {
  102. "Content-Type": type
  103. },
  104. body: attData
  105. });
  106. T(xhr.status === 201);
  107. doc._rev = JSON.parse(xhr.responseText).rev;
  108. }
  109. function compareObjects(o1, o2) {
  110. for (var p in o1) {
  111. if (o1[p] === null && o2[p] !== null) {
  112. return false;
  113. } else if (typeof o1[p] === "object") {
  114. if ((typeof o2[p] !== "object") || o2[p] === null) {
  115. return false;
  116. }
  117. if (!arguments.callee(o1[p], o2[p])) {
  118. return false;
  119. }
  120. } else {
  121. if (o1[p] !== o2[p]) {
  122. return false;
  123. }
  124. }
  125. }
  126. return true;
  127. }
  128. function waitForSeq(sourceDb, targetDb) {
  129. var targetSeq,
  130. sourceSeq = sourceDb.info().update_seq,
  131. t0 = new Date(),
  132. t1,
  133. ms = 3000;
  134. do {
  135. targetSeq = targetDb.info().update_seq;
  136. t1 = new Date();
  137. } while (((t1 - t0) <= ms) && targetSeq < sourceSeq);
  138. }
  139. function wait(ms) {
  140. var t0 = new Date(), t1;
  141. do {
  142. CouchDB.request("GET", "/");
  143. t1 = new Date();
  144. } while ((t1 - t0) <= ms);
  145. }
  146. // test simple replications (not continuous, not filtered), including
  147. // conflict creation
  148. docs = makeDocs(1, 21);
  149. docs.push({
  150. _id: "_design/foo",
  151. language: "javascript",
  152. value: "ddoc"
  153. });
  154. for (i = 0; i < dbPairs.length; i++) {
  155. populateDb(sourceDb, docs);
  156. populateDb(targetDb, []);
  157. // add some attachments
  158. for (j = 10; j < 15; j++) {
  159. addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
  160. }
  161. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  162. TEquals(true, repResult.ok);
  163. sourceInfo = sourceDb.info();
  164. targetInfo = targetDb.info();
  165. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  166. TEquals('string', typeof repResult.session_id);
  167. TEquals(repResult.source_last_seq, sourceInfo.update_seq);
  168. TEquals(true, repResult.history instanceof Array);
  169. TEquals(1, repResult.history.length);
  170. TEquals(repResult.history[0].session_id, repResult.session_id);
  171. TEquals('string', typeof repResult.history[0].start_time);
  172. TEquals('string', typeof repResult.history[0].end_time);
  173. TEquals(0, repResult.history[0].start_last_seq);
  174. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  175. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  176. TEquals(sourceInfo.doc_count, repResult.history[0].missing_checked);
  177. TEquals(sourceInfo.doc_count, repResult.history[0].missing_found);
  178. TEquals(sourceInfo.doc_count, repResult.history[0].docs_read);
  179. TEquals(sourceInfo.doc_count, repResult.history[0].docs_written);
  180. TEquals(0, repResult.history[0].doc_write_failures);
  181. for (j = 0; j < docs.length; j++) {
  182. doc = docs[j];
  183. copy = targetDb.open(doc._id);
  184. T(copy !== null);
  185. TEquals(true, compareObjects(doc, copy));
  186. if (j >= 10 && j < 15) {
  187. var atts = copy._attachments;
  188. TEquals('object', typeof atts);
  189. TEquals('object', typeof atts["readme.txt"]);
  190. TEquals(2, atts["readme.txt"].revpos);
  191. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  192. TEquals(true, atts["readme.txt"].stub);
  193. var att_copy = CouchDB.request(
  194. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  195. ).responseText;
  196. TEquals(att1_data.length, att_copy.length);
  197. TEquals(att1_data, att_copy);
  198. }
  199. }
  200. // add one more doc to source, more attachments to some existing docs
  201. // and replicate again
  202. var newDoc = {
  203. _id: "foo666",
  204. value: "d"
  205. };
  206. TEquals(true, sourceDb.save(newDoc).ok);
  207. // add some more attachments
  208. for (j = 10; j < 15; j++) {
  209. addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
  210. }
  211. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  212. TEquals(true, repResult.ok);
  213. sourceInfo = sourceDb.info();
  214. targetInfo = targetDb.info();
  215. TEquals(targetInfo.doc_count, sourceInfo.doc_count);
  216. TEquals('string', typeof repResult.session_id);
  217. TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  218. TEquals(true, repResult.history instanceof Array);
  219. TEquals(2, repResult.history.length);
  220. TEquals(repResult.history[0].session_id, repResult.session_id);
  221. TEquals('string', typeof repResult.history[0].start_time);
  222. TEquals('string', typeof repResult.history[0].end_time);
  223. TEquals((sourceInfo.update_seq - 6), repResult.history[0].start_last_seq);
  224. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  225. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  226. TEquals(6, repResult.history[0].missing_checked);
  227. TEquals(6, repResult.history[0].missing_found);
  228. TEquals(6, repResult.history[0].docs_read);
  229. TEquals(6, repResult.history[0].docs_written);
  230. TEquals(0, repResult.history[0].doc_write_failures);
  231. copy = targetDb.open(newDoc._id);
  232. T(copy !== null);
  233. TEquals(newDoc._id, copy._id);
  234. TEquals(newDoc.value, copy.value);
  235. for (j = 10; j < 15; j++) {
  236. doc = docs[j];
  237. copy = targetDb.open(doc._id);
  238. T(copy !== null);
  239. TEquals(true, compareObjects(doc, copy));
  240. var atts = copy._attachments;
  241. TEquals('object', typeof atts);
  242. TEquals('object', typeof atts["readme.txt"]);
  243. TEquals(2, atts["readme.txt"].revpos);
  244. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  245. TEquals(true, atts["readme.txt"].stub);
  246. var att1_copy = CouchDB.request(
  247. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  248. ).responseText;
  249. TEquals(att1_data.length, att1_copy.length);
  250. TEquals(att1_data, att1_copy);
  251. TEquals('object', typeof atts["data.dat"]);
  252. TEquals(3, atts["data.dat"].revpos);
  253. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  254. TEquals(true, atts["data.dat"].stub);
  255. var att2_copy = CouchDB.request(
  256. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  257. ).responseText;
  258. TEquals(att2_data.length, att2_copy.length);
  259. TEquals(att2_data, att2_copy);
  260. }
  261. // test deletion is replicated
  262. doc = sourceDb.open(docs[1]._id);
  263. TEquals(true, sourceDb.deleteDoc(doc).ok);
  264. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  265. TEquals(true, repResult.ok);
  266. sourceInfo = sourceDb.info();
  267. targetInfo = targetDb.info();
  268. TEquals(targetInfo.doc_count, sourceInfo.doc_count);
  269. TEquals(targetInfo.doc_del_count, sourceInfo.doc_del_count);
  270. TEquals(1, targetInfo.doc_del_count);
  271. TEquals(true, repResult.history instanceof Array);
  272. TEquals(3, repResult.history.length);
  273. TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  274. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  275. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  276. TEquals(1, repResult.history[0].missing_checked);
  277. TEquals(1, repResult.history[0].missing_found);
  278. TEquals(1, repResult.history[0].docs_read);
  279. TEquals(1, repResult.history[0].docs_written);
  280. TEquals(0, repResult.history[0].doc_write_failures);
  281. copy = targetDb.open(docs[1]._id);
  282. TEquals(null, copy);
  283. var changes = targetDb.changes({since: 0});
  284. var idx = changes.results.length - 1;
  285. TEquals(docs[1]._id, changes.results[idx].id);
  286. TEquals(true, changes.results[idx].deleted);
  287. // test conflict
  288. doc = sourceDb.open(docs[0]._id);
  289. doc.value = "white";
  290. TEquals(true, sourceDb.save(doc).ok);
  291. copy = targetDb.open(docs[0]._id);
  292. copy.value = "black";
  293. TEquals(true, targetDb.save(copy).ok);
  294. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  295. TEquals(true, repResult.ok);
  296. sourceInfo = sourceDb.info();
  297. targetInfo = targetDb.info();
  298. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  299. TEquals(true, repResult.history instanceof Array);
  300. TEquals(4, repResult.history.length);
  301. TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  302. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  303. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  304. TEquals(1, repResult.history[0].missing_checked);
  305. TEquals(1, repResult.history[0].missing_found);
  306. TEquals(1, repResult.history[0].docs_read);
  307. TEquals(1, repResult.history[0].docs_written);
  308. TEquals(0, repResult.history[0].doc_write_failures);
  309. copy = targetDb.open(docs[0]._id, {conflicts: true});
  310. TEquals(0, copy._rev.indexOf("2-"));
  311. TEquals(true, copy._conflicts instanceof Array);
  312. TEquals(1, copy._conflicts.length);
  313. TEquals(0, copy._conflicts[0].indexOf("2-"));
  314. // replicate again with conflict
  315. doc.value = "yellow";
  316. TEquals(true, sourceDb.save(doc).ok);
  317. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  318. TEquals(true, repResult.ok);
  319. sourceInfo = sourceDb.info();
  320. targetInfo = targetDb.info();
  321. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  322. TEquals(true, repResult.history instanceof Array);
  323. TEquals(5, repResult.history.length);
  324. TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  325. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  326. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  327. TEquals(1, repResult.history[0].missing_checked);
  328. TEquals(1, repResult.history[0].missing_found);
  329. TEquals(1, repResult.history[0].docs_read);
  330. TEquals(1, repResult.history[0].docs_written);
  331. TEquals(0, repResult.history[0].doc_write_failures);
  332. copy = targetDb.open(docs[0]._id, {conflicts: true});
  333. TEquals(0, copy._rev.indexOf("3-"));
  334. TEquals(true, copy._conflicts instanceof Array);
  335. TEquals(1, copy._conflicts.length);
  336. TEquals(0, copy._conflicts[0].indexOf("2-"));
  337. // resolve the conflict
  338. TEquals(true, targetDb.deleteDoc({_id: copy._id, _rev: copy._conflicts[0]}).ok);
  339. // replicate again, check there are no more conflicts
  340. doc.value = "rainbow";
  341. TEquals(true, sourceDb.save(doc).ok);
  342. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  343. TEquals(true, repResult.ok);
  344. sourceInfo = sourceDb.info();
  345. targetInfo = targetDb.info();
  346. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  347. TEquals(true, repResult.history instanceof Array);
  348. TEquals(6, repResult.history.length);
  349. TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  350. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  351. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  352. TEquals(1, repResult.history[0].missing_checked);
  353. TEquals(1, repResult.history[0].missing_found);
  354. TEquals(1, repResult.history[0].docs_read);
  355. TEquals(1, repResult.history[0].docs_written);
  356. TEquals(0, repResult.history[0].doc_write_failures);
  357. copy = targetDb.open(docs[0]._id, {conflicts: true});
  358. TEquals(0, copy._rev.indexOf("4-"));
  359. TEquals('undefined', typeof copy._conflicts);
  360. // test that revisions already in a target are not copied
  361. TEquals(true, sourceDb.save({_id: "foo1", value: 111}).ok);
  362. TEquals(true, targetDb.save({_id: "foo1", value: 111}).ok);
  363. TEquals(true, sourceDb.save({_id: "foo2", value: 222}).ok);
  364. TEquals(true, sourceDb.save({_id: "foo3", value: 333}).ok);
  365. TEquals(true, targetDb.save({_id: "foo3", value: 333}).ok);
  366. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  367. TEquals(true, repResult.ok);
  368. sourceInfo = sourceDb.info();
  369. TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  370. TEquals(sourceInfo.update_seq - 3, repResult.history[0].start_last_seq);
  371. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  372. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  373. TEquals(3, repResult.history[0].missing_checked);
  374. TEquals(1, repResult.history[0].missing_found);
  375. TEquals(1, repResult.history[0].docs_read);
  376. TEquals(1, repResult.history[0].docs_written);
  377. TEquals(0, repResult.history[0].doc_write_failures);
  378. TEquals(true, sourceDb.save({_id: "foo4", value: 444}).ok);
  379. TEquals(true, targetDb.save({_id: "foo4", value: 444}).ok);
  380. TEquals(true, sourceDb.save({_id: "foo5", value: 555}).ok);
  381. TEquals(true, targetDb.save({_id: "foo5", value: 555}).ok);
  382. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  383. TEquals(true, repResult.ok);
  384. sourceInfo = sourceDb.info();
  385. TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  386. TEquals(sourceInfo.update_seq - 2, repResult.history[0].start_last_seq);
  387. TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  388. TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  389. TEquals(2, repResult.history[0].missing_checked);
  390. TEquals(0, repResult.history[0].missing_found);
  391. TEquals(0, repResult.history[0].docs_read);
  392. TEquals(0, repResult.history[0].docs_written);
  393. TEquals(0, repResult.history[0].doc_write_failures);
  394. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  395. TEquals(true, repResult.ok);
  396. TEquals(true, repResult.no_changes);
  397. sourceInfo = sourceDb.info();
  398. TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  399. }
  400. // test error when source database does not exist
  401. try {
  402. CouchDB.replicate("foobar", "test_suite_db");
  403. T(false, "should have failed with db_not_found error");
  404. } catch (x) {
  405. TEquals("db_not_found", x.error);
  406. }
  407. try {
  408. CouchDB.replicate(CouchDB.protocol + host + "/foobar", "test_suite_db");
  409. T(false, "should have failed with db_not_found error");
  410. } catch (x) {
  411. TEquals("db_not_found", x.error);
  412. }
  413. // test since_seq parameter
  414. docs = makeDocs(1, 6);
  415. for (i = 0; i < dbPairs.length; i++) {
  416. var since_seq = 3;
  417. populateDb(sourceDb, docs);
  418. populateDb(targetDb, []);
  419. var expected_ids = [];
  420. var changes = sourceDb.changes({since: since_seq});
  421. for (j = 0; j < changes.results.length; j++) {
  422. expected_ids.push(changes.results[j].id);
  423. }
  424. TEquals(2, expected_ids.length, "2 documents since since_seq");
  425. // For OTP < R14B03, temporary child specs are kept in the supervisor
  426. // after the child terminates, so cancel the replication to delete the
  427. // child spec in those OTP releases, otherwise since_seq will have no
  428. // effect.
  429. try {
  430. CouchDB.replicate(
  431. dbPairs[i].source,
  432. dbPairs[i].target,
  433. {body: {cancel: true}}
  434. );
  435. } catch (x) {
  436. // OTP R14B03 onwards
  437. TEquals("not found", x.error);
  438. }
  439. repResult = CouchDB.replicate(
  440. dbPairs[i].source,
  441. dbPairs[i].target,
  442. {body: {since_seq: since_seq}}
  443. );
  444. // Same reason as before. But here we don't want since_seq to affect
  445. // subsequent replications, so we need to delete the child spec from the
  446. // supervisor (since_seq is not used to calculate the replication ID).
  447. try {
  448. CouchDB.replicate(
  449. dbPairs[i].source,
  450. dbPairs[i].target,
  451. {body: {cancel: true}}
  452. );
  453. } catch (x) {
  454. // OTP R14B03 onwards
  455. TEquals("not found", x.error);
  456. }
  457. TEquals(true, repResult.ok);
  458. TEquals(2, repResult.history[0].missing_checked);
  459. TEquals(2, repResult.history[0].missing_found);
  460. TEquals(2, repResult.history[0].docs_read);
  461. TEquals(2, repResult.history[0].docs_written);
  462. TEquals(0, repResult.history[0].doc_write_failures);
  463. for (j = 0; j < docs.length; j++) {
  464. doc = docs[j];
  465. copy = targetDb.open(doc._id);
  466. if (expected_ids.indexOf(doc._id) === -1) {
  467. T(copy === null);
  468. } else {
  469. T(copy !== null);
  470. TEquals(true, compareObjects(doc, copy));
  471. }
  472. }
  473. }
  474. // test errors due to doc validate_doc_update functions in the target endpoint
  475. docs = makeDocs(1, 8);
  476. docs[2]["_attachments"] = {
  477. "hello.txt": {
  478. "content_type": "text/plain",
  479. "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
  480. }
  481. };
  482. var ddoc = {
  483. _id: "_design/test",
  484. language: "javascript",
  485. validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) {
  486. if ((newDoc.integer % 2) !== 0) {
  487. throw {forbidden: "I only like multiples of 2."};
  488. }
  489. }).toString()
  490. };
  491. for (i = 0; i < dbPairs.length; i++) {
  492. populateDb(sourceDb, docs);
  493. populateDb(targetDb, [ddoc]);
  494. repResult = CouchDB.replicate(
  495. dbPairs[i].source,
  496. dbPairs[i].target
  497. );
  498. TEquals(true, repResult.ok);
  499. TEquals(7, repResult.history[0].missing_checked);
  500. TEquals(7, repResult.history[0].missing_found);
  501. TEquals(7, repResult.history[0].docs_read);
  502. TEquals(3, repResult.history[0].docs_written);
  503. TEquals(4, repResult.history[0].doc_write_failures);
  504. for (j = 0; j < docs.length; j++) {
  505. doc = docs[j];
  506. copy = targetDb.open(doc._id);
  507. if (doc.integer % 2 === 0) {
  508. T(copy !== null);
  509. TEquals(copy.integer, doc.integer);
  510. } else {
  511. T(copy === null);
  512. }
  513. }
  514. }
  515. // test create_target option
  516. docs = makeDocs(1, 2);
  517. for (i = 0; i < dbPairs.length; i++) {
  518. populateDb(sourceDb, docs);
  519. targetDb.deleteDb();
  520. repResult = CouchDB.replicate(
  521. dbPairs[i].source,
  522. dbPairs[i].target,
  523. {body: {create_target: true}}
  524. );
  525. TEquals(true, repResult.ok);
  526. sourceInfo = sourceDb.info();
  527. targetInfo = targetDb.info();
  528. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  529. TEquals(sourceInfo.update_seq, targetInfo.update_seq);
  530. }
  531. // test filtered replication
  532. docs = makeDocs(1, 31);
  533. docs.push({
  534. _id: "_design/mydesign",
  535. language: "javascript",
  536. filters: {
  537. myfilter: (function(doc, req) {
  538. var modulus = Number(req.query.modulus);
  539. var special = req.query.special;
  540. return (doc.integer % modulus === 0) || (doc.string === special);
  541. }).toString()
  542. }
  543. });
  544. for (i = 0; i < dbPairs.length; i++) {
  545. populateDb(sourceDb, docs);
  546. populateDb(targetDb, []);
  547. repResult = CouchDB.replicate(
  548. dbPairs[i].source,
  549. dbPairs[i].target,
  550. {
  551. body: {
  552. filter: "mydesign/myfilter",
  553. query_params: {
  554. modulus: "2",
  555. special: "7"
  556. }
  557. }
  558. }
  559. );
  560. TEquals(true, repResult.ok);
  561. for (j = 0; j < docs.length; j++) {
  562. doc = docs[j];
  563. copy = targetDb.open(doc._id);
  564. if ((doc.integer && (doc.integer % 2 === 0)) || (doc.string === "7")) {
  565. T(copy !== null);
  566. TEquals(true, compareObjects(doc, copy));
  567. } else {
  568. TEquals(null, copy);
  569. }
  570. }
  571. TEquals(true, repResult.history instanceof Array);
  572. TEquals(1, repResult.history.length);
  573. // NOT 31 (31 is db seq for last doc - the ddoc, which was not replicated)
  574. TEquals(30, repResult.source_last_seq);
  575. TEquals(0, repResult.history[0].start_last_seq);
  576. TEquals(30, repResult.history[0].end_last_seq);
  577. TEquals(30, repResult.history[0].recorded_seq);
  578. // 16 => 15 docs with even integer field + 1 doc with string field "7"
  579. TEquals(16, repResult.history[0].missing_checked);
  580. TEquals(16, repResult.history[0].missing_found);
  581. TEquals(16, repResult.history[0].docs_read);
  582. TEquals(16, repResult.history[0].docs_written);
  583. TEquals(0, repResult.history[0].doc_write_failures);
  584. // add new docs to source and resume the same replication
  585. var newDocs = makeDocs(50, 56);
  586. populateDb(sourceDb, newDocs, true);
  587. repResult = CouchDB.replicate(
  588. dbPairs[i].source,
  589. dbPairs[i].target,
  590. {
  591. body: {
  592. filter: "mydesign/myfilter",
  593. query_params: {
  594. modulus: "2",
  595. special: "7"
  596. }
  597. }
  598. }
  599. );
  600. TEquals(true, repResult.ok);
  601. for (j = 0; j < newDocs.length; j++) {
  602. doc = newDocs[j];
  603. copy = targetDb.open(doc._id);
  604. if (doc.integer && (doc.integer % 2 === 0)) {
  605. T(copy !== null);
  606. TEquals(true, compareObjects(doc, copy));
  607. } else {
  608. TEquals(null, copy);
  609. }
  610. }
  611. // last doc has even integer field, so last replicated seq is 36
  612. TEquals(36, repResult.source_last_seq);
  613. TEquals(true, repResult.history instanceof Array);
  614. TEquals(2, repResult.history.length);
  615. TEquals(30, repResult.history[0].start_last_seq);
  616. TEquals(36, repResult.history[0].end_last_seq);
  617. TEquals(36, repResult.history[0].recorded_seq);
  618. TEquals(3, repResult.history[0].missing_checked);
  619. TEquals(3, repResult.history[0].missing_found);
  620. TEquals(3, repResult.history[0].docs_read);
  621. TEquals(3, repResult.history[0].docs_written);
  622. TEquals(0, repResult.history[0].doc_write_failures);
  623. }
  624. // test filtered replication works as expected after changing the filter's
  625. // code (ticket COUCHDB-892)
  626. var filterFun1 = (function(doc, req) {
  627. if (doc.value < Number(req.query.maxvalue)) {
  628. return true;
  629. } else {
  630. return false;
  631. }
  632. }).toString();
  633. var filterFun2 = (function(doc, req) {
  634. return true;
  635. }).toString();
  636. for (i = 0; i < dbPairs.length; i++) {
  637. populateDb(targetDb, []);
  638. populateDb(sourceDb, []);
  639. TEquals(true, sourceDb.save({_id: "foo1", value: 1}).ok);
  640. TEquals(true, sourceDb.save({_id: "foo2", value: 2}).ok);
  641. TEquals(true, sourceDb.save({_id: "foo3", value: 3}).ok);
  642. TEquals(true, sourceDb.save({_id: "foo4", value: 4}).ok);
  643. var ddoc = {
  644. "_id": "_design/mydesign",
  645. "language": "javascript",
  646. "filters": {
  647. "myfilter": filterFun1
  648. }
  649. };
  650. TEquals(true, sourceDb.save(ddoc).ok);
  651. repResult = CouchDB.replicate(
  652. dbPairs[i].source,
  653. dbPairs[i].target,
  654. {
  655. body: {
  656. filter: "mydesign/myfilter",
  657. query_params: {
  658. maxvalue: "3"
  659. }
  660. }
  661. }
  662. );
  663. TEquals(true, repResult.ok);
  664. TEquals(true, repResult.history instanceof Array);
  665. TEquals(1, repResult.history.length);
  666. TEquals(2, repResult.history[0].docs_written);
  667. TEquals(2, repResult.history[0].docs_read);
  668. TEquals(0, repResult.history[0].doc_write_failures);
  669. var docFoo1 = targetDb.open("foo1");
  670. T(docFoo1 !== null);
  671. TEquals(1, docFoo1.value);
  672. var docFoo2 = targetDb.open("foo2");
  673. T(docFoo2 !== null);
  674. TEquals(2, docFoo2.value);
  675. var docFoo3 = targetDb.open("foo3");
  676. TEquals(null, docFoo3);
  677. var docFoo4 = targetDb.open("foo4");
  678. TEquals(null, docFoo4);
  679. // replication should start from scratch after the filter's code changed
  680. ddoc.filters.myfilter = filterFun2;
  681. TEquals(true, sourceDb.save(ddoc).ok);
  682. repResult = CouchDB.replicate(
  683. dbPairs[i].source,
  684. dbPairs[i].target,
  685. {
  686. body: {
  687. filter: "mydesign/myfilter",
  688. query_params : {
  689. maxvalue: "3"
  690. }
  691. }
  692. }
  693. );
  694. TEquals(true, repResult.ok);
  695. TEquals(true, repResult.history instanceof Array);
  696. TEquals(1, repResult.history.length);
  697. TEquals(3, repResult.history[0].docs_written);
  698. TEquals(3, repResult.history[0].docs_read);
  699. TEquals(0, repResult.history[0].doc_write_failures);
  700. docFoo1 = targetDb.open("foo1");
  701. T(docFoo1 !== null);
  702. TEquals(1, docFoo1.value);
  703. docFoo2 = targetDb.open("foo2");
  704. T(docFoo2 !== null);
  705. TEquals(2, docFoo2.value);
  706. docFoo3 = targetDb.open("foo3");
  707. T(docFoo3 !== null);
  708. TEquals(3, docFoo3.value);
  709. docFoo4 = targetDb.open("foo4");
  710. T(docFoo4 !== null);
  711. TEquals(4, docFoo4.value);
  712. T(targetDb.open("_design/mydesign") !== null);
  713. }
  714. // test replication by doc IDs
  715. docs = makeDocs(1, 11);
  716. docs.push({
  717. _id: "_design/foo",
  718. language: "javascript",
  719. integer: 1
  720. });
  721. var target_doc_ids = [
  722. { initial: ["1", "2", "10"], after: [], conflict_id: "2" },
  723. { initial: ["1", "2"], after: ["7"], conflict_id: "1" },
  724. { initial: ["1", "foo_666", "10"], after: ["7"], conflict_id: "10" },
  725. { initial: ["_design/foo", "8"], after: ["foo_5"], conflict_id: "8" },
  726. { initial: ["_design%2Ffoo", "8"], after: ["foo_5"], conflict_id: "8" },
  727. { initial: [], after: ["foo_1000", "_design/foo", "1"], conflict_id: "1" }
  728. ];
  729. var doc_ids, after_doc_ids;
  730. var id, num_inexistent_docs, after_num_inexistent_docs;
  731. var total, after_total;
  732. for (i = 0; i < dbPairs.length; i++) {
  733. for (j = 0; j < target_doc_ids.length; j++) {
  734. doc_ids = target_doc_ids[j].initial;
  735. num_inexistent_docs = 0;
  736. for (k = 0; k < doc_ids.length; k++) {
  737. id = doc_ids[k];
  738. if (id.indexOf("foo_") === 0) {
  739. num_inexistent_docs += 1;
  740. }
  741. }
  742. populateDb(sourceDb, docs);
  743. populateDb(targetDb, []);
  744. repResult = CouchDB.replicate(
  745. dbPairs[i].source,
  746. dbPairs[i].target,
  747. {
  748. body: {
  749. doc_ids: doc_ids
  750. }
  751. }
  752. );
  753. total = doc_ids.length - num_inexistent_docs;
  754. TEquals(true, repResult.ok);
  755. if (total === 0) {
  756. TEquals(true, repResult.no_changes);
  757. } else {
  758. TEquals('string', typeof repResult.start_time);
  759. TEquals('string', typeof repResult.end_time);
  760. TEquals(total, repResult.docs_read);
  761. TEquals(total, repResult.docs_written);
  762. TEquals(0, repResult.doc_write_failures);
  763. }
  764. for (k = 0; k < doc_ids.length; k++) {
  765. id = decodeURIComponent(doc_ids[k]);
  766. doc = sourceDb.open(id);
  767. copy = targetDb.open(id);
  768. if (id.indexOf("foo_") === 0) {
  769. TEquals(null, doc);
  770. TEquals(null, copy);
  771. } else {
  772. T(doc !== null);
  773. T(copy !== null);
  774. TEquals(true, compareObjects(doc, copy));
  775. }
  776. }
  777. // be absolutely sure that other docs were not replicated
  778. for (k = 0; k < docs.length; k++) {
  779. var base_id = docs[k]._id;
  780. id = encodeURIComponent(base_id);
  781. doc = targetDb.open(base_id);
  782. if ((doc_ids.indexOf(id) >= 0) || (doc_ids.indexOf(base_id) >= 0)) {
  783. T(doc !== null);
  784. } else {
  785. TEquals(null, doc);
  786. }
  787. }
  788. targetInfo = targetDb.info();
  789. TEquals(total, targetInfo.doc_count);
  790. // add more docs throught replication by doc IDs
  791. after_doc_ids = target_doc_ids[j].after;
  792. after_num_inexistent_docs = 0;
  793. for (k = 0; k < after_doc_ids.length; k++) {
  794. id = after_doc_ids[k];
  795. if (id.indexOf("foo_") === 0) {
  796. after_num_inexistent_docs += 1;
  797. }
  798. }
  799. repResult = CouchDB.replicate(
  800. dbPairs[i].source,
  801. dbPairs[i].target,
  802. {
  803. body: {
  804. doc_ids: after_doc_ids
  805. }
  806. }
  807. );
  808. after_total = after_doc_ids.length - after_num_inexistent_docs;
  809. TEquals(true, repResult.ok);
  810. if (after_total === 0) {
  811. TEquals(true, repResult.no_changes);
  812. } else {
  813. TEquals('string', typeof repResult.start_time);
  814. TEquals('string', typeof repResult.end_time);
  815. TEquals(after_total, repResult.docs_read);
  816. TEquals(after_total, repResult.docs_written);
  817. TEquals(0, repResult.doc_write_failures);
  818. }
  819. for (k = 0; k < after_doc_ids.length; k++) {
  820. id = after_doc_ids[k];
  821. doc = sourceDb.open(id);
  822. copy = targetDb.open(id);
  823. if (id.indexOf("foo_") === 0) {
  824. TEquals(null, doc);
  825. TEquals(null, copy);
  826. } else {
  827. T(doc !== null);
  828. T(copy !== null);
  829. TEquals(true, compareObjects(doc, copy));
  830. }
  831. }
  832. // be absolutely sure that other docs were not replicated
  833. for (k = 0; k < docs.length; k++) {
  834. var base_id = docs[k]._id;
  835. id = encodeURIComponent(base_id);
  836. doc = targetDb.open(base_id);
  837. if ((doc_ids.indexOf(id) >= 0) || (after_doc_ids.indexOf(id) >= 0) ||
  838. (doc_ids.indexOf(base_id) >= 0) ||
  839. (after_doc_ids.indexOf(base_id) >= 0)) {
  840. T(doc !== null);
  841. } else {
  842. TEquals(null, doc);
  843. }
  844. }
  845. targetInfo = targetDb.info();
  846. TEquals((total + after_total), targetInfo.doc_count);
  847. // replicate again the same doc after updated on source (no conflict)
  848. id = target_doc_ids[j].conflict_id;
  849. doc = sourceDb.open(id);
  850. T(doc !== null);
  851. doc.integer = 666;
  852. TEquals(true, sourceDb.save(doc).ok);
  853. addAtt(sourceDb, doc, "readme.txt", att1_data, "text/plain");
  854. addAtt(sourceDb, doc, "data.dat", att2_data, "application/binary");
  855. repResult = CouchDB.replicate(
  856. dbPairs[i].source,
  857. dbPairs[i].target,
  858. {
  859. body: {
  860. doc_ids: [id]
  861. }
  862. }
  863. );
  864. TEquals(true, repResult.ok);
  865. TEquals(1, repResult.docs_read);
  866. TEquals(1, repResult.docs_written);
  867. TEquals(0, repResult.doc_write_failures);
  868. copy = targetDb.open(id, {conflicts: true});
  869. TEquals(666, copy.integer);
  870. TEquals(0, copy._rev.indexOf("4-"));
  871. TEquals('undefined', typeof copy._conflicts);
  872. var atts = copy._attachments;
  873. TEquals('object', typeof atts);
  874. TEquals('object', typeof atts["readme.txt"]);
  875. TEquals(3, atts["readme.txt"].revpos);
  876. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  877. TEquals(true, atts["readme.txt"].stub);
  878. var att1_copy = CouchDB.request(
  879. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  880. ).responseText;
  881. TEquals(att1_data.length, att1_copy.length);
  882. TEquals(att1_data, att1_copy);
  883. TEquals('object', typeof atts["data.dat"]);
  884. TEquals(4, atts["data.dat"].revpos);
  885. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  886. TEquals(true, atts["data.dat"].stub);
  887. var att2_copy = CouchDB.request(
  888. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  889. ).responseText;
  890. TEquals(att2_data.length, att2_copy.length);
  891. TEquals(att2_data, att2_copy);
  892. // generate a conflict throught replication by doc IDs
  893. id = target_doc_ids[j].conflict_id;
  894. doc = sourceDb.open(id);
  895. copy = targetDb.open(id);
  896. T(doc !== null);
  897. T(copy !== null);
  898. doc.integer += 100;
  899. copy.integer += 1;
  900. TEquals(true, sourceDb.save(doc).ok);
  901. TEquals(true, targetDb.save(copy).ok);
  902. repResult = CouchDB.replicate(
  903. dbPairs[i].source,
  904. dbPairs[i].target,
  905. {
  906. body: {
  907. doc_ids: [id]
  908. }
  909. }
  910. );
  911. TEquals(true, repResult.ok);
  912. TEquals(1, repResult.docs_read);
  913. TEquals(1, repResult.docs_written);
  914. TEquals(0, repResult.doc_write_failures);
  915. copy = targetDb.open(id, {conflicts: true});
  916. TEquals(0, copy._rev.indexOf("5-"));
  917. TEquals(true, copy._conflicts instanceof Array);
  918. TEquals(1, copy._conflicts.length);
  919. TEquals(0, copy._conflicts[0].indexOf("5-"));
  920. }
  921. }
  922. docs = makeDocs(1, 25);
  923. docs.push({
  924. _id: "_design/foo",
  925. language: "javascript",
  926. filters: {
  927. myfilter: (function(doc, req) { return true; }).toString()
  928. }
  929. });
  930. for (i = 0; i < dbPairs.length; i++) {
  931. populateDb(sourceDb, docs);
  932. populateDb(targetDb, []);
  933. // add some attachments
  934. for (j = 10; j < 15; j++) {
  935. addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
  936. }
  937. repResult = CouchDB.replicate(
  938. dbPairs[i].source,
  939. dbPairs[i].target,
  940. {
  941. body: {
  942. continuous: true
  943. }
  944. }
  945. );
  946. TEquals(true, repResult.ok);
  947. TEquals('string', typeof repResult._local_id);
  948. var rep_id = repResult._local_id;
  949. waitForSeq(sourceDb, targetDb);
  950. for (j = 0; j < docs.length; j++) {
  951. doc = docs[j];
  952. copy = targetDb.open(doc._id);
  953. T(copy !== null);
  954. TEquals(true, compareObjects(doc, copy));
  955. if (j >= 10 && j < 15) {
  956. var atts = copy._attachments;
  957. TEquals('object', typeof atts);
  958. TEquals('object', typeof atts["readme.txt"]);
  959. TEquals(2, atts["readme.txt"].revpos);
  960. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  961. TEquals(true, atts["readme.txt"].stub);
  962. var att_copy = CouchDB.request(
  963. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  964. ).responseText;
  965. TEquals(att1_data.length, att_copy.length);
  966. TEquals(att1_data, att_copy);
  967. }
  968. }
  969. sourceInfo = sourceDb.info();
  970. targetInfo = targetDb.info();
  971. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  972. // add attachments to docs in source
  973. for (j = 10; j < 15; j++) {
  974. addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
  975. }
  976. var ddoc = docs[docs.length - 1]; // design doc
  977. addAtt(sourceDb, ddoc, "readme.txt", att1_data, "text/plain");
  978. waitForSeq(sourceDb, targetDb);
  979. var modifDocs = docs.slice(10, 15).concat([ddoc]);
  980. for (j = 0; j < modifDocs.length; j++) {
  981. doc = modifDocs[j];
  982. copy = targetDb.open(doc._id);
  983. T(copy !== null);
  984. TEquals(true, compareObjects(doc, copy));
  985. var atts = copy._attachments;
  986. TEquals('object', typeof atts);
  987. TEquals('object', typeof atts["readme.txt"]);
  988. TEquals(2, atts["readme.txt"].revpos);
  989. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  990. TEquals(true, atts["readme.txt"].stub);
  991. var att1_copy = CouchDB.request(
  992. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  993. ).responseText;
  994. TEquals(att1_data.length, att1_copy.length);
  995. TEquals(att1_data, att1_copy);
  996. if (doc._id.indexOf("_design/") === -1) {
  997. TEquals('object', typeof atts["data.dat"]);
  998. TEquals(3, atts["data.dat"].revpos);
  999. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  1000. TEquals(true, atts["data.dat"].stub);
  1001. var att2_copy = CouchDB.request(
  1002. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  1003. ).responseText;
  1004. TEquals(att2_data.length, att2_copy.length);
  1005. TEquals(att2_data, att2_copy);
  1006. }
  1007. }
  1008. sourceInfo = sourceDb.info();
  1009. targetInfo = targetDb.info();
  1010. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1011. // add another attachment to the ddoc on source
  1012. addAtt(sourceDb, ddoc, "data.dat", att2_data, "application/binary");
  1013. waitForSeq(sourceDb, targetDb);
  1014. copy = targetDb.open(ddoc._id);
  1015. var atts = copy._attachments;
  1016. TEquals('object', typeof atts);
  1017. TEquals('object', typeof atts["readme.txt"]);
  1018. TEquals(2, atts["readme.txt"].revpos);
  1019. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  1020. TEquals(true, atts["readme.txt"].stub);
  1021. var att1_copy = CouchDB.request(
  1022. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  1023. ).responseText;
  1024. TEquals(att1_data.length, att1_copy.length);
  1025. TEquals(att1_data, att1_copy);
  1026. TEquals('object', typeof atts["data.dat"]);
  1027. TEquals(3, atts["data.dat"].revpos);
  1028. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  1029. TEquals(true, atts["data.dat"].stub);
  1030. var att2_copy = CouchDB.request(
  1031. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  1032. ).responseText;
  1033. TEquals(att2_data.length, att2_copy.length);
  1034. TEquals(att2_data, att2_copy);
  1035. sourceInfo = sourceDb.info();
  1036. targetInfo = targetDb.info();
  1037. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1038. // add more docs to source
  1039. var newDocs = makeDocs(25, 35);
  1040. populateDb(sourceDb, newDocs, true);
  1041. waitForSeq(sourceDb, targetDb);
  1042. for (j = 0; j < newDocs.length; j++) {
  1043. doc = newDocs[j];
  1044. copy = targetDb.open(doc._id);
  1045. T(copy !== null);
  1046. TEquals(true, compareObjects(doc, copy));
  1047. }
  1048. sourceInfo = sourceDb.info();
  1049. targetInfo = targetDb.info();
  1050. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1051. // delete docs from source
  1052. TEquals(true, sourceDb.deleteDoc(newDocs[0]).ok);
  1053. wait(1000);
  1054. TEquals(true, sourceDb.deleteDoc(newDocs[6]).ok);
  1055. waitForSeq(sourceDb, targetDb);
  1056. copy = targetDb.open(newDocs[0]._id);
  1057. TEquals(null, copy);
  1058. copy = targetDb.open(newDocs[6]._id);
  1059. TEquals(null, copy);
  1060. var changes = targetDb.changes({since: targetInfo.update_seq});
  1061. var line1 = changes.results[changes.results.length - 2];
  1062. var line2 = changes.results[changes.results.length - 1];
  1063. TEquals(newDocs[0]._id, line1.id);
  1064. TEquals(true, line1.deleted);
  1065. TEquals(newDocs[6]._id, line2.id);
  1066. TEquals(true, line2.deleted);
  1067. // cancel the replication
  1068. repResult = CouchDB.replicate(
  1069. dbPairs[i].source,
  1070. dbPairs[i].target,
  1071. {
  1072. body: {
  1073. continuous: true,
  1074. cancel: true
  1075. }
  1076. }
  1077. );
  1078. TEquals(true, repResult.ok);
  1079. TEquals(rep_id, repResult._local_id);
  1080. doc = {
  1081. _id: 'foobar',
  1082. value: 666
  1083. };
  1084. TEquals(true, sourceDb.save(doc).ok);
  1085. wait(2000);
  1086. copy = targetDb.open(doc._id);
  1087. TEquals(null, copy);
  1088. }
  1089. // COUCHDB-1093 - filtered and continuous _changes feed dies when the
  1090. // database is compacted
  1091. docs = makeDocs(1, 10);
  1092. docs.push({
  1093. _id: "_design/foo",
  1094. language: "javascript",
  1095. filters: {
  1096. myfilter: (function(doc, req) { return true; }).toString()
  1097. }
  1098. });
  1099. populateDb(sourceDb, docs);
  1100. populateDb(targetDb, []);
  1101. repResult = CouchDB.replicate(
  1102. CouchDB.protocol + host + "/" + sourceDb.name,
  1103. targetDb.name,
  1104. {
  1105. body: {
  1106. continuous: true,
  1107. filter: "foo/myfilter"
  1108. }
  1109. }
  1110. );
  1111. TEquals(true, repResult.ok);
  1112. TEquals('string', typeof repResult._local_id);
  1113. var xhr = CouchDB.request("GET", "/_active_tasks");
  1114. var tasks = JSON.parse(xhr.responseText);
  1115. TEquals(true, sourceDb.compact().ok);
  1116. while (sourceDb.info().compact_running) {};
  1117. TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok);
  1118. xhr = CouchDB.request("GET", "/_active_tasks");
  1119. var tasksAfter = JSON.parse(xhr.responseText);
  1120. TEquals(tasks.length, tasksAfter.length);
  1121. waitForSeq(sourceDb, targetDb);
  1122. T(sourceDb.open("30") !== null);
  1123. // cancel replication
  1124. repResult = CouchDB.replicate(
  1125. CouchDB.protocol + host + "/" + sourceDb.name,
  1126. targetDb.name,
  1127. {
  1128. body: {
  1129. continuous: true,
  1130. filter: "foo/myfilter",
  1131. cancel: true
  1132. }
  1133. }
  1134. );
  1135. TEquals(true, repResult.ok);
  1136. TEquals('string', typeof repResult._local_id);
  1137. //
  1138. // test replication of compressed attachments
  1139. //
  1140. doc = {
  1141. _id: "foobar"
  1142. };
  1143. var bigTextAtt = makeAttData(128 * 1024);
  1144. var attName = "readme.txt";
  1145. var xhr = CouchDB.request("GET", "/_config/attachments/compression_level");
  1146. var compressionLevel = JSON.parse(xhr.responseText);
  1147. xhr = CouchDB.request("GET", "/_config/attachments/compressible_types");
  1148. var compressibleTypes = JSON.parse(xhr.responseText);
  1149. for (i = 0; i < dbPairs.length; i++) {
  1150. populateDb(sourceDb, [doc]);
  1151. populateDb(targetDb, []);
  1152. // enable compression of text types
  1153. enableAttCompression("8", "text/*");
  1154. // add text attachment to foobar doc
  1155. xhr = CouchDB.request(
  1156. "PUT",
  1157. "/" + sourceDb.name + "/" + doc._id + "/" + attName + "?rev=" + doc._rev,
  1158. {
  1159. body: bigTextAtt,
  1160. headers: {"Content-Type": "text/plain"}
  1161. }
  1162. );
  1163. TEquals(201, xhr.status);
  1164. // disable compression and replicate
  1165. disableAttCompression();
  1166. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  1167. TEquals(true, repResult.ok);
  1168. TEquals(true, repResult.history instanceof Array);
  1169. TEquals(1, repResult.history.length);
  1170. TEquals(1, repResult.history[0].missing_checked);
  1171. TEquals(1, repResult.history[0].missing_found);
  1172. TEquals(1, repResult.history[0].docs_read);
  1173. TEquals(1, repResult.history[0].docs_written);
  1174. TEquals(0, repResult.history[0].doc_write_failures);
  1175. copy = targetDb.open(
  1176. doc._id,
  1177. {att_encoding_info: true, bypass_cache: Math.round(Math.random() * 1000)}
  1178. );
  1179. T(copy !== null);
  1180. T(attName in copy._attachments);
  1181. TEquals("gzip", copy._attachments[attName].encoding);
  1182. TEquals("number", typeof copy._attachments[attName].length);
  1183. TEquals("number", typeof copy._attachments[attName].encoded_length);
  1184. T(copy._attachments[attName].encoded_length < copy._attachments[attName].length);
  1185. }
  1186. delete bigTextAtt;
  1187. // restore original settings
  1188. enableAttCompression(compressionLevel, compressibleTypes);
  1189. //
  1190. // test replication triggered by non admins
  1191. //
  1192. // case 1) user triggering the replication is not a DB admin of the target DB
  1193. var joeUserDoc = CouchDB.prepareUserDoc({
  1194. name: "joe",
  1195. roles: ["erlanger"]
  1196. }, "erly");
  1197. var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
  1198. var server_config = [
  1199. {
  1200. section: "couch_httpd_auth",
  1201. key: "authentication_db",
  1202. value: usersDb.name
  1203. }
  1204. ];
  1205. docs = makeDocs(1, 6);
  1206. docs.push({
  1207. _id: "_design/foo",
  1208. language: "javascript"
  1209. });
  1210. dbPairs = [
  1211. {
  1212. source: sourceDb.name,
  1213. target: targetDb.name
  1214. },
  1215. {
  1216. source: CouchDB.protocol + host + "/" + sourceDb.name,
  1217. target: targetDb.name
  1218. },
  1219. {
  1220. source: sourceDb.name,
  1221. target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
  1222. },
  1223. {
  1224. source: CouchDB.protocol + host + "/" + sourceDb.name,
  1225. target: CouchDB.protocol + "joe:erly@" + host + "/" + targetDb.name
  1226. }
  1227. ];
  1228. for (i = 0; i < dbPairs.length; i++) {
  1229. usersDb.deleteDb();
  1230. populateDb(sourceDb, docs);
  1231. populateDb(targetDb, []);
  1232. TEquals(true, targetDb.setSecObj({
  1233. admins: {
  1234. names: ["superman"],
  1235. roles: ["god"]
  1236. }
  1237. }).ok);
  1238. run_on_modified_server(server_config, function() {
  1239. delete joeUserDoc._rev;
  1240. TEquals(true, usersDb.save(joeUserDoc).ok);
  1241. TEquals(true, CouchDB.login("joe", "erly").ok);
  1242. TEquals('joe', CouchDB.session().userCtx.name);
  1243. repResult = CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  1244. TEquals(true, CouchDB.logout().ok);
  1245. TEquals(true, repResult.ok);
  1246. TEquals(docs.length, repResult.history[0].docs_read);
  1247. TEquals((docs.length - 1), repResult.history[0].docs_written); // 1 ddoc
  1248. TEquals(1, repResult.history[0].doc_write_failures);
  1249. });
  1250. for (j = 0; j < docs.length; j++) {
  1251. doc = docs[j];
  1252. copy = targetDb.open(doc._id);
  1253. if (doc._id.indexOf("_design/") === 0) {
  1254. TEquals(null, copy);
  1255. } else {
  1256. T(copy !== null);
  1257. TEquals(true, compareObjects(doc, copy));
  1258. }
  1259. }
  1260. }
  1261. // case 2) user triggering the replication is not a reader (nor admin) of the
  1262. // source DB
  1263. dbPairs = [
  1264. {
  1265. source: sourceDb.name,
  1266. target: targetDb.name
  1267. },
  1268. {
  1269. source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
  1270. target: targetDb.name
  1271. },
  1272. {
  1273. source: sourceDb.name,
  1274. target: CouchDB.protocol + host + "/" + targetDb.name
  1275. },
  1276. {
  1277. source: CouchDB.protocol + "joe:erly@" + host + "/" + sourceDb.name,
  1278. target: CouchDB.protocol + host + "/" + targetDb.name
  1279. }
  1280. ];
  1281. for (i = 0; i < dbPairs.length; i++) {
  1282. usersDb.deleteDb();
  1283. populateDb(sourceDb, docs);
  1284. populateDb(targetDb, []);
  1285. TEquals(true, sourceDb.setSecObj({
  1286. admins: {
  1287. names: ["superman"],
  1288. roles: ["god"]
  1289. },
  1290. readers: {
  1291. names: ["john"],
  1292. roles: ["secret"]
  1293. }
  1294. }).ok);
  1295. run_on_modified_server(server_config, function() {
  1296. delete joeUserDoc._rev;
  1297. TEquals(true, usersDb.save(joeUserDoc).ok);
  1298. TEquals(true, CouchDB.login("joe", "erly").ok);
  1299. TEquals('joe', CouchDB.session().userCtx.name);
  1300. try {
  1301. CouchDB.replicate(dbPairs[i].source, dbPairs[i].target);
  1302. T(false, "should have raised an exception");
  1303. } catch (x) {
  1304. TEquals("unauthorized", x.error);
  1305. }
  1306. TEquals(true, CouchDB.logout().ok);
  1307. });
  1308. for (j = 0; j < docs.length; j++) {
  1309. doc = docs[j];
  1310. copy = targetDb.open(doc._id);
  1311. TEquals(null, copy);
  1312. }
  1313. }
  1314. // COUCHDB-885 - push replication of a doc with attachment causes a
  1315. // conflict in the target.
  1316. sourceDb = new CouchDB("test_suite_db_a");
  1317. targetDb = new CouchDB("test_suite_db_b");
  1318. sourceDb.deleteDb();
  1319. sourceDb.createDb();
  1320. targetDb.deleteDb();
  1321. targetDb.createDb();
  1322. doc = {
  1323. _id: "doc1"
  1324. };
  1325. TEquals(true, sourceDb.save(doc).ok);
  1326. repResult = CouchDB.replicate(
  1327. sourceDb.name,
  1328. CouchDB.protocol + host + "/" + targetDb.name
  1329. );
  1330. TEquals(true, repResult.ok);
  1331. TEquals(true, repResult.history instanceof Array);
  1332. TEquals(1, repResult.history.length);
  1333. TEquals(1, repResult.history[0].docs_written);
  1334. TEquals(1, repResult.history[0].docs_read);
  1335. TEquals(0, repResult.history[0].doc_write_failures);
  1336. doc["_attachments"] = {
  1337. "hello.txt": {
  1338. "content_type": "text/plain",
  1339. "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
  1340. },
  1341. "foo.dat": {
  1342. "content_type": "not/compressible",
  1343. "data": "aSBhbSBub3QgZ3ppcGVk" // base64:encode("i am not gziped")
  1344. }
  1345. };
  1346. TEquals(true, sourceDb.save(doc).ok);
  1347. repResult = CouchDB.replicate(
  1348. sourceDb.name,
  1349. CouchDB.protocol + host + "/" + targetDb.name
  1350. );
  1351. TEquals(true, repResult.ok);
  1352. TEquals(true, repResult.history instanceof Array);
  1353. TEquals(2, repResult.history.length);
  1354. TEquals(1, repResult.history[0].docs_written);
  1355. TEquals(1, repResult.history[0].docs_read);
  1356. TEquals(0, repResult.history[0].doc_write_failures);
  1357. copy = targetDb.open(doc._id, {
  1358. conflicts: true, deleted_conflicts: true,
  1359. attachments: true, att_encoding_info: true});
  1360. T(copy !== null);
  1361. TEquals("undefined", typeof copy._conflicts);
  1362. TEquals("undefined", typeof copy._deleted_conflicts);
  1363. TEquals("text/plain", copy._attachments["hello.txt"]["content_type"]);
  1364. TEquals("aGVsbG8gd29ybGQ=", copy._attachments["hello.txt"]["data"]);
  1365. TEquals("gzip", copy._attachments["hello.txt"]["encoding"]);
  1366. TEquals("not/compressible", copy._attachments["foo.dat"]["content_type"]);
  1367. TEquals("aSBhbSBub3QgZ3ppcGVk", copy._attachments["foo.dat"]["data"]);
  1368. TEquals("undefined", typeof copy._attachments["foo.dat"]["encoding"]);
  1369. // end of test for COUCHDB-885
  1370. // Test for COUCHDB-1242 (reject non-string query_params)
  1371. try {
  1372. CouchDB.replicate(sourceDb, targetDb, {
  1373. body: {
  1374. filter : "mydesign/myfilter",
  1375. query_params : {
  1376. "maxvalue": 4
  1377. }
  1378. }
  1379. });
  1380. } catch (e) {
  1381. TEquals("bad_request", e.error);
  1382. }
  1383. // Test that we can cancel a replication just by POSTin…

Large files files are truncated, but you can click here to view the full file