PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/test/javascript/tests/changes.js

http://github.com/apache/couchdb
JavaScript | 812 lines | 464 code | 114 blank | 234 comment | 100 complexity | 2f6c7ef8a3f1c25662873fa8945a2a26 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  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.elixir = true;
  13. function jsonp(obj) {
  14. T(jsonp_flag == 0);
  15. T(obj.results.length == 1 && obj.last_seq == 1, "jsonp");
  16. jsonp_flag = 1;
  17. }
  18. couchTests.changes = function(debug) {
  19. return console.log('done in test/elixir/test/changes_test.exs and changes_async_test.exs');
  20. var db;
  21. if (debug) debugger;
  22. // poor man's browser detection
  23. var is_safari = false;
  24. if (typeof (navigator) == "undefined") {
  25. is_safari = true; // For CouchHTTP based runners
  26. } else if (navigator.userAgent.match(/AppleWebKit/)) {
  27. is_safari = true;
  28. }
  29. testChanges("live");
  30. testChanges("continuous");
  31. function testChanges(feed) {
  32. var db_name = get_random_db_name();
  33. // (write-quorums help keep a consistent feed)
  34. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  35. db.createDb();
  36. var req = CouchDB.request("GET", "/" + db_name + "/_changes");
  37. var resp = JSON.parse(req.responseText);
  38. TEquals(0, resp.results.length, "db must be empty")
  39. TEquals("0", resp.last_seq.substr(0, 1), "seq must start with 0")
  40. var docFoo = {_id:"foo", bar:1};
  41. T(db.save(docFoo).ok);
  42. T(db.ensureFullCommit().ok);
  43. T(db.open(docFoo._id)._id == docFoo._id);
  44. retry_part(function(){ // avoid Heisenbugs
  45. req = CouchDB.request("GET", "/" + db_name + "/_changes");
  46. var resp = JSON.parse(req.responseText);
  47. TEquals("1", resp.last_seq.substr(0, 1), "seq must start with 1");
  48. T(resp.results.length == 1, "one doc db");
  49. T(resp.results[0].changes[0].rev == docFoo._rev);
  50. });
  51. // test with callback
  52. // TODO: either allow jsonp in the default global config or implement a config chg mechanism analogouts 2 sebastianrothbucher:clustertest - or leave out
  53. // run_on_modified_server(
  54. // [{section: "httpd",
  55. // key: "allow_jsonp",
  56. // value: "true"}],
  57. // function() {
  58. // var xhr = CouchDB.request("GET", "/" + db_name + "/_changes?callback=jsonp");
  59. // T(xhr.status == 200);
  60. // jsonp_flag = 0;
  61. // eval(xhr.responseText);
  62. // T(jsonp_flag == 1);
  63. // });
  64. // increase timeout to 100 to have enough time 2 assemble (seems like too little timeouts kill
  65. req = CouchDB.request("GET", "/" + db_name + "/_changes?feed=" + feed + "&timeout=100");
  66. var lines = req.responseText.split("\n");
  67. T(JSON.parse(lines[0]).changes[0].rev == docFoo._rev);
  68. // the sequence is not fully ordered and a complex structure now
  69. T(JSON.parse(lines[1]).last_seq[0] == 1);
  70. var xhr;
  71. try {
  72. xhr = CouchDB.newXhr();
  73. } catch (err) {
  74. }
  75. // these will NEVER run as we're always in navigator == undefined
  76. if (!is_safari && xhr) {
  77. // Only test the continuous stuff if we have a real XHR object
  78. // with real async support.
  79. // WebKit (last checked on nightly #47686) does fail on processing
  80. // the async-request properly while javascript is executed.
  81. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=" + feed + "&timeout=500"), true);
  82. xhr.send("");
  83. var docBar = {_id:"bar", bar:1};
  84. db.save(docBar);
  85. var lines, change1, change2;
  86. waitForSuccess(function() {
  87. lines = xhr.responseText.split("\n");
  88. change1 = JSON.parse(lines[0]);
  89. change2 = JSON.parse(lines[1]);
  90. if (change2.seq != 2) {
  91. throw "bad seq, try again";
  92. }
  93. return true;
  94. }, "bar-only");
  95. T(change1.seq == 1);
  96. T(change1.id == "foo");
  97. T(change2.seq == 2);
  98. T(change2.id == "bar");
  99. T(change2.changes[0].rev == docBar._rev);
  100. var docBaz = {_id:"baz", baz:1};
  101. db.save(docBaz);
  102. var change3;
  103. waitForSuccess(function() {
  104. lines = xhr.responseText.split("\n");
  105. change3 = JSON.parse(lines[2]);
  106. if (change3.seq != 3) {
  107. throw "bad seq, try again";
  108. }
  109. return true;
  110. });
  111. T(change3.seq == 3);
  112. T(change3.id == "baz");
  113. T(change3.changes[0].rev == docBaz._rev);
  114. xhr = CouchDB.newXhr();
  115. //verify the heartbeat newlines are sent
  116. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=" + feed + "&heartbeat=10&timeout=500"), true);
  117. xhr.send("");
  118. var str;
  119. waitForSuccess(function() {
  120. str = xhr.responseText;
  121. if (str.charAt(str.length - 1) != "\n" || str.charAt(str.length - 2) != "\n") {
  122. throw("keep waiting");
  123. }
  124. return true;
  125. }, "heartbeat");
  126. T(str.charAt(str.length - 1) == "\n");
  127. T(str.charAt(str.length - 2) == "\n");
  128. // otherwise we'll continue to receive heartbeats forever
  129. xhr.abort();
  130. }
  131. db.deleteDb();
  132. }
  133. // these will NEVER run as we're always in navigator == undefined
  134. if (!is_safari && xhr) {
  135. // test Server Sent Event (eventsource)
  136. if (!!window.EventSource) {
  137. var source = new EventSource(
  138. "/" + db_name + "/_changes?feed=eventsource");
  139. var results = [];
  140. var sourceListener = function(e) {
  141. var data = JSON.parse(e.data);
  142. results.push(data);
  143. };
  144. source.addEventListener('message', sourceListener , false);
  145. waitForSuccess(function() {
  146. if (results.length != 3) {
  147. throw "bad seq, try again";
  148. }
  149. return true;
  150. });
  151. source.removeEventListener('message', sourceListener, false);
  152. T(results[0].seq == 1);
  153. T(results[0].id == "foo");
  154. T(results[1].seq == 2);
  155. T(results[1].id == "bar");
  156. T(results[1].changes[0].rev == docBar._rev);
  157. }
  158. // test that we receive EventSource heartbeat events
  159. if (!!window.EventSource) {
  160. var source = new EventSource(
  161. "/" + db_name + "/_changes?feed=eventsource&heartbeat=10");
  162. var count_heartbeats = 0;
  163. source.addEventListener('heartbeat', function () { count_heartbeats = count_heartbeats + 1; } , false);
  164. waitForSuccess(function() {
  165. if (count_heartbeats < 3) {
  166. throw "keep waiting";
  167. }
  168. return true;
  169. }, "eventsource-heartbeat");
  170. T(count_heartbeats >= 3);
  171. source.close();
  172. }
  173. // test longpolling
  174. xhr = CouchDB.newXhr();
  175. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=longpoll"), true);
  176. xhr.send("");
  177. waitForSuccess(function() {
  178. lines = xhr.responseText.split("\n");
  179. if (lines[5] != '"last_seq":3}') {
  180. throw("still waiting");
  181. }
  182. return true;
  183. }, "last_seq");
  184. xhr = CouchDB.newXhr();
  185. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=longpoll&since=3"), true);
  186. xhr.send("");
  187. var docBarz = {_id:"barz", bar:1};
  188. db.save(docBarz);
  189. var parse_changes_line = function(line) {
  190. if (line.charAt(line.length-1) == ",") {
  191. var linetrimmed = line.substring(0, line.length-1);
  192. } else {
  193. var linetrimmed = line;
  194. }
  195. return JSON.parse(linetrimmed);
  196. };
  197. waitForSuccess(function() {
  198. lines = xhr.responseText.split("\n");
  199. if (lines[3] != '"last_seq":4}') {
  200. throw("still waiting");
  201. }
  202. return true;
  203. }, "change_lines");
  204. var change = parse_changes_line(lines[1]);
  205. T(change.seq == 4);
  206. T(change.id == "barz");
  207. T(change.changes[0].rev == docBarz._rev);
  208. T(lines[3]=='"last_seq":4}');
  209. // test since=now
  210. xhr = CouchDB.newXhr();
  211. xhr.open("GET", "/" + db_name + "/_changes?feed=longpoll&since=now", true);
  212. xhr.send("");
  213. var docBarz = {_id:"barzzzz", bar:1};
  214. db.save(docBarz);
  215. var parse_changes_line = function(line) {
  216. if (line.charAt(line.length-1) == ",") {
  217. var linetrimmed = line.substring(0, line.length-1);
  218. } else {
  219. var linetrimmed = line;
  220. }
  221. return JSON.parse(linetrimmed);
  222. };
  223. waitForSuccess(function() {
  224. lines = xhr.responseText.split("\n");
  225. if (lines[3] != '"last_seq":5}') {
  226. throw("still waiting");
  227. }
  228. return true;
  229. }, "change_lines");
  230. var change = parse_changes_line(lines[1]);
  231. T(change.seq == 5);
  232. T(change.id == "barzzzz");
  233. T(change.changes[0].rev == docBarz._rev);
  234. T(lines[3]=='"last_seq":5}');
  235. }
  236. db.deleteDb();
  237. // test on a new DB
  238. var db_name = get_random_db_name();
  239. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  240. db.createDb();
  241. // test the filtered changes
  242. var ddoc = {
  243. _id : "_design/changes_filter",
  244. "filters" : {
  245. "bop" : "function(doc, req) { return (doc.bop);}",
  246. "dynamic" : stringFun(function(doc, req) {
  247. var field = req.query.field;
  248. return doc[field];
  249. }),
  250. "userCtx" : stringFun(function(doc, req) {
  251. return doc.user && (doc.user == req.userCtx.name);
  252. }),
  253. "conflicted" : "function(doc, req) { return (doc._conflicts);}"
  254. },
  255. options : {
  256. local_seq : true
  257. },
  258. views : {
  259. local_seq : {
  260. map : "function(doc) {emit(doc._local_seq, null)}"
  261. },
  262. blah: {
  263. map : 'function(doc) {' +
  264. ' if (doc._id == "blah") {' +
  265. ' emit(null, null);' +
  266. ' }' +
  267. '}'
  268. }
  269. }
  270. };
  271. db.save(ddoc);
  272. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/bop");
  273. var resp = JSON.parse(req.responseText);
  274. T(resp.results.length == 0);
  275. var docres1 = db.save({"bop" : "foom"});
  276. T(docres1.ok);
  277. var docres2 = db.save({"bop" : false});
  278. T(docres2.ok);
  279. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/bop");
  280. var resp = JSON.parse(req.responseText);
  281. var seqold = resp.results[0].seq;
  282. T(resp.results.length == 1, "filtered/bop");
  283. T(resp.results[0].changes[0].rev == docres1.rev, "filtered/bop rev");
  284. // save and reload (substitute for all those parts that never run)
  285. var chgdoc1 = db.open(docres1.id);
  286. chgdoc1.newattr = "s/th new";
  287. docres1 = db.save(chgdoc1);
  288. T(docres1.ok);
  289. req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/bop");
  290. resp = JSON.parse(req.responseText);
  291. var seqchg = resp.results[0].seq;
  292. T(resp.results.length == 1, "filtered/bop new");
  293. T(resp.results[0].changes[0].rev == docres1.rev, "filtered/bop rev new");
  294. T(seqold != seqchg, "filtered/bop new seq number");
  295. req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/dynamic&field=woox");
  296. resp = JSON.parse(req.responseText);
  297. T(resp.results.length == 0);
  298. req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/dynamic&field=bop");
  299. resp = JSON.parse(req.responseText);
  300. T(resp.results.length == 1, "changes_filter/dynamic&field=bop");
  301. T(resp.results[0].changes[0].rev == docres1.rev, "filtered/dynamic&field=bop rev");
  302. // these will NEVER run as we're always in navigator == undefined
  303. if (!is_safari && xhr) { // full test requires parallel connections
  304. // filter with longpoll
  305. // longpoll filters full history when run without a since seq
  306. xhr = CouchDB.newXhr();
  307. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=longpoll&filter=changes_filter/bop"), false);
  308. xhr.send("");
  309. var resp = JSON.parse(xhr.responseText);
  310. T(resp.last_seq == 8);
  311. // longpoll waits until a matching change before returning
  312. xhr = CouchDB.newXhr();
  313. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=longpoll&since=7&filter=changes_filter/bop"), true);
  314. xhr.send("");
  315. db.save({"_id":"falsy", "bop" : ""}); // empty string is falsy
  316. db.save({"_id":"bingo","bop" : "bingo"});
  317. waitForSuccess(function() {
  318. resp = JSON.parse(xhr.responseText);
  319. return true;
  320. }, "longpoll-since");
  321. T(resp.last_seq == 10);
  322. T(resp.results && resp.results.length > 0 && resp.results[0]["id"] == "bingo", "filter the correct update");
  323. xhr.abort();
  324. var timeout = 500;
  325. var last_seq = 11;
  326. while (true) {
  327. // filter with continuous
  328. xhr = CouchDB.newXhr();
  329. xhr.open("GET", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=continuous&filter=changes_filter/bop&timeout="+timeout), true);
  330. xhr.send("");
  331. db.save({"_id":"rusty", "bop" : "plankton"});
  332. T(xhr.readyState != 4, "test client too slow");
  333. var rusty = db.open("rusty", {cache_bust : new Date()});
  334. T(rusty._id == "rusty");
  335. waitForSuccess(function() { // throws an error after 5 seconds
  336. if (xhr.readyState != 4) {
  337. throw("still waiting");
  338. }
  339. return true;
  340. }, "continuous-rusty");
  341. lines = xhr.responseText.split("\n");
  342. var good = false;
  343. try {
  344. JSON.parse(lines[3]);
  345. good = true;
  346. } catch(e) {
  347. }
  348. if (good) {
  349. T(JSON.parse(lines[1]).id == "bingo", lines[1]);
  350. T(JSON.parse(lines[2]).id == "rusty", lines[2]);
  351. T(JSON.parse(lines[3]).last_seq == last_seq, lines[3]);
  352. break;
  353. } else {
  354. xhr.abort();
  355. db.deleteDoc(rusty);
  356. timeout = timeout * 2;
  357. last_seq = last_seq + 2;
  358. }
  359. }
  360. }
  361. // error conditions
  362. // non-existing design doc
  363. var req = CouchDB.request("GET",
  364. "/" + db_name + "/_changes?filter=nothingtosee/bop");
  365. TEquals(404, req.status, "should return 404 for non existant design doc");
  366. // non-existing filter
  367. var req = CouchDB.request("GET",
  368. "/" + db_name + "/_changes?filter=changes_filter/movealong");
  369. TEquals(404, req.status, "should return 404 for non existant filter fun");
  370. // both
  371. var req = CouchDB.request("GET",
  372. "/" + db_name + "/_changes?filter=nothingtosee/movealong");
  373. TEquals(404, req.status,
  374. "should return 404 for non existant design doc and filter fun");
  375. // changes get all_docs style with deleted docs
  376. var doc = {a:1};
  377. db.save(doc);
  378. db.deleteDoc(doc);
  379. var req = CouchDB.request("GET",
  380. "/" + db_name + "/_changes?filter=changes_filter/bop&style=all_docs");
  381. var resp = JSON.parse(req.responseText);
  382. var expect = (!is_safari && xhr) ? 3: 1;
  383. TEquals(expect, resp.results.length, "should return matching rows");
  384. // test filter on view function (map)
  385. //
  386. T(db.save({"_id":"blah", "bop" : "plankton"}).ok);
  387. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=_view&view=changes_filter/blah");
  388. var resp = JSON.parse(req.responseText);
  389. T(resp.results.length === 1);
  390. T(resp.results[0].id === "blah");
  391. // test for userCtx
  392. // TODO: either make part of global config, or allow 4 config changes - or leave out
  393. /*
  394. run_on_modified_server(
  395. [{section: "httpd",
  396. key: "authentication_handlers",
  397. value: "{couch_httpd_auth, special_test_authentication_handler}"},
  398. {section:"httpd",
  399. key: "WWW-Authenticate",
  400. value: "X-Couch-Test-Auth"}],
  401. function() {
  402. var authOpts = {"headers":{"WWW-Authenticate": "X-Couch-Test-Auth Chris Anderson:mp3"}};
  403. var req = CouchDB.request("GET", "/_session", authOpts);
  404. var resp = JSON.parse(req.responseText);
  405. T(db.save({"user" : "Noah Slater"}).ok);
  406. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/userCtx", authOpts);
  407. var resp = JSON.parse(req.responseText);
  408. T(resp.results.length == 0);
  409. var docResp = db.save({"user" : "Chris Anderson"});
  410. T(docResp.ok);
  411. T(db.ensureFullCommit().ok);
  412. req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/userCtx", authOpts);
  413. resp = JSON.parse(req.responseText);
  414. T(resp.results.length == 1, "userCtx");
  415. T(resp.results[0].id == docResp.id);
  416. }
  417. );
  418. */
  419. req = CouchDB.request("GET", "/" + db_name + "/_changes?limit=1");
  420. resp = JSON.parse(req.responseText);
  421. TEquals(1, resp.results.length);
  422. //filter includes _conflicts
  423. // TODO: all_or_nothing not yet in place
  424. // var id = db.save({'food' : 'pizza'}).id;
  425. // db.bulkSave([{_id: id, 'food' : 'pasta'}], {all_or_nothing:true});
  426. //
  427. // req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=changes_filter/conflicted");
  428. // resp = JSON.parse(req.responseText);
  429. // T(resp.results.length == 1, "filter=changes_filter/conflicted");
  430. // test with erlang filter function
  431. // TODO: either make part of global config, or allow 4 config changes - or leave out
  432. /*
  433. run_on_modified_server([{
  434. section: "native_query_servers",
  435. key: "erlang",
  436. value: "{couch_native_process, start_link, []}"
  437. }], function() {
  438. var erl_ddoc = {
  439. _id: "_design/erlang",
  440. language: "erlang",
  441. filters: {
  442. foo:
  443. 'fun({Doc}, Req) -> ' +
  444. ' case couch_util:get_value(<<"value">>, Doc) of' +
  445. ' undefined -> false;' +
  446. ' Value -> (Value rem 2) =:= 0;' +
  447. ' _ -> false' +
  448. ' end ' +
  449. 'end.'
  450. }
  451. };
  452. db.deleteDb();
  453. db.createDb();
  454. T(db.save(erl_ddoc).ok);
  455. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=erlang/foo");
  456. var resp = JSON.parse(req.responseText);
  457. T(resp.results.length === 0);
  458. T(db.save({_id: "doc1", value : 1}).ok);
  459. T(db.save({_id: "doc2", value : 2}).ok);
  460. T(db.save({_id: "doc3", value : 3}).ok);
  461. T(db.save({_id: "doc4", value : 4}).ok);
  462. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=erlang/foo");
  463. var resp = JSON.parse(req.responseText);
  464. T(resp.results.length === 2);
  465. T(resp.results[0].id === "doc2");
  466. T(resp.results[1].id === "doc4");
  467. // test filtering on docids
  468. //
  469. var options = {
  470. headers: {"Content-Type": "application/json"},
  471. body: JSON.stringify({"doc_ids": ["something", "anotherthing", "andmore"]})
  472. };
  473. var req = CouchDB.request("POST", "/" + db_name + "/_changes?filter=_doc_ids", options);
  474. var resp = JSON.parse(req.responseText);
  475. T(resp.results.length === 0);
  476. T(db.save({"_id":"something", "bop" : "plankton"}).ok);
  477. var req = CouchDB.request("POST", "/" + db_name + "/_changes?filter=_doc_ids", options);
  478. var resp = JSON.parse(req.responseText);
  479. T(resp.results.length === 1);
  480. T(resp.results[0].id === "something");
  481. T(db.save({"_id":"anotherthing", "bop" : "plankton"}).ok);
  482. var req = CouchDB.request("POST", "/" + db_name + "/_changes?filter=_doc_ids", options);
  483. var resp = JSON.parse(req.responseText);
  484. T(resp.results.length === 2);
  485. T(resp.results[0].id === "something");
  486. T(resp.results[1].id === "anotherthing");
  487. var docids = JSON.stringify(["something", "anotherthing", "andmore"]),
  488. req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=_doc_ids&doc_ids="+docids, options);
  489. var resp = JSON.parse(req.responseText);
  490. T(resp.results.length === 2);
  491. T(resp.results[0].id === "something");
  492. T(resp.results[1].id === "anotherthing");
  493. var req = CouchDB.request("GET", "/" + db_name + "/_changes?filter=_design");
  494. var resp = JSON.parse(req.responseText);
  495. T(resp.results.length === 1);
  496. T(resp.results[0].id === "_design/erlang");
  497. if (!is_safari && xhr) {
  498. // filter docids with continuous
  499. xhr = CouchDB.newXhr();
  500. xhr.open("POST", CouchDB.proxyUrl("/" + db_name + "/_changes?feed=continuous&timeout=500&since=7&filter=_doc_ids"), true);
  501. xhr.setRequestHeader("Content-Type", "application/json");
  502. xhr.send(options.body);
  503. T(db.save({"_id":"andmore", "bop" : "plankton"}).ok);
  504. waitForSuccess(function() {
  505. if (xhr.readyState != 4) {
  506. throw("still waiting");
  507. }
  508. return true;
  509. }, "andmore-only");
  510. var line = JSON.parse(xhr.responseText.split("\n")[0]);
  511. T(line.seq == 8);
  512. T(line.id == "andmore");
  513. }
  514. });
  515. */
  516. db.deleteDb();
  517. // COUCHDB-1037 - empty result for ?limit=1&filter=foo/bar in some cases
  518. // test w/ new temp DB
  519. db_name = get_random_db_name();
  520. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  521. T(db.createDb());
  522. ddoc = {
  523. _id: "_design/testdocs",
  524. filters: {
  525. testdocsonly: (function(doc, req) {
  526. return (typeof doc.integer === "number");
  527. }).toString()
  528. }
  529. };
  530. T(db.save(ddoc));
  531. ddoc = {
  532. _id: "_design/foobar",
  533. foo: "bar"
  534. };
  535. T(db.save(ddoc));
  536. db.bulkSave(makeDocs(0, 5));
  537. // for n>1 you can't be sure all docs are there immediately - so either stick w/ -n 1 or implement check-wait-check or use the quorum (for now, the latter seems 2 suffice)
  538. req = CouchDB.request("GET", "/" + db.name + "/_changes");
  539. resp = JSON.parse(req.responseText);
  540. // you can't know wether 7 is the last seq as you don't know how many collapse into one number
  541. //TEquals(7, resp.last_seq);
  542. TEquals(7, resp.results.length);
  543. req = CouchDB.request(
  544. "GET", "/"+ db.name + "/_changes?limit=1&filter=testdocs/testdocsonly");
  545. resp = JSON.parse(req.responseText);
  546. // (seq as before)
  547. //TEquals(3, resp.last_seq);
  548. TEquals(1, resp.results.length);
  549. // also, we can't guarantee ordering
  550. T(resp.results[0].id.match("[0-5]"));
  551. req = CouchDB.request(
  552. "GET", "/" + db.name + "/_changes?limit=2&filter=testdocs/testdocsonly");
  553. resp = JSON.parse(req.responseText);
  554. // (seq as before)
  555. //TEquals(4, resp.last_seq);
  556. TEquals(2, resp.results.length);
  557. // also, we can't guarantee ordering
  558. T(resp.results[0].id.match("[0-5]"));
  559. T(resp.results[1].id.match("[0-5]"));
  560. // TODO: either use local port for stats (and aggregate when n>1) or leave out
  561. // TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value);
  562. // CouchDB.request("GET", "/" + db.name + "/_changes");
  563. // TEquals(0, CouchDB.requestStats(['couchdb', 'httpd', 'clients_requesting_changes'], true).value);
  564. db.deleteDb();
  565. // COUCHDB-1256
  566. // test w/ new temp DB
  567. db_name = get_random_db_name();
  568. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  569. T(db.createDb());
  570. T(db.save({"_id":"foo", "a" : 123}).ok);
  571. T(db.save({"_id":"bar", "a" : 456}).ok);
  572. options = {
  573. headers: {"Content-Type": "application/json"},
  574. body: JSON.stringify({"_rev":"1-cc609831f0ca66e8cd3d4c1e0d98108a", "a":456})
  575. };
  576. req = CouchDB.request("PUT", "/" + db.name + "/foo?new_edits=false", options);
  577. req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs");
  578. resp = JSON.parse(req.responseText);
  579. // (seq as before)
  580. //TEquals(3, resp.last_seq);
  581. TEquals(2, resp.results.length);
  582. // we can no longer pass a number into 'since' - but we have the 2nd last above - so we can use it (puh!)
  583. req = CouchDB.request("GET", "/" + db.name + "/_changes?style=all_docs&since=" + encodeURIComponent(resp.results[0].seq));
  584. resp = JSON.parse(req.responseText);
  585. // (seq as before)
  586. //TEquals(3, resp.last_seq);
  587. TEquals(1, resp.results.length);
  588. // TEquals(2, resp.results[0].changes.length);
  589. db.deleteDb();
  590. // COUCHDB-1852
  591. // test w/ new temp DB
  592. db_name = get_random_db_name();
  593. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  594. T(db.createDb());
  595. // create 4 documents... this assumes the update sequnce will start from 0 and then do sth in the cluster
  596. db.save({"bop" : "foom"});
  597. db.save({"bop" : "foom"});
  598. db.save({"bop" : "foom"});
  599. db.save({"bop" : "foom"});
  600. // because of clustering, we need the 2nd entry as since value
  601. req = CouchDB.request("GET", "/" + db_name + "/_changes");
  602. // simulate an EventSource request with a Last-Event-ID header
  603. // increase timeout to 100 to have enough time 2 assemble (seems like too little timeouts kill
  604. req = CouchDB.request("GET", "/" + db_name + "/_changes?feed=eventsource&timeout=100&since=0",
  605. {"headers": {"Accept": "text/event-stream", "Last-Event-ID": JSON.parse(req.responseText).results[1].seq}});
  606. // "parse" the eventsource response and collect only the "id: ..." lines
  607. var changes = req.responseText.split('\n')
  608. .map(function (el) {
  609. return el.split(":").map(function (el) { return el.trim()});
  610. })
  611. .filter(function (el) { return (el[0] === "id"); })
  612. // make sure we only got 2 changes, and they are update_seq=3 and update_seq=4
  613. T(changes.length === 2);
  614. // seq is different now
  615. //T(changes[0][1] === "3");
  616. //T(changes[1][1] === "4");
  617. db.deleteDb();
  618. // COUCHDB-1923
  619. // test w/ new temp DB
  620. db_name = get_random_db_name();
  621. db = new CouchDB(db_name, {"X-Couch-Full-Commit":"true"}, {"w": 3});
  622. T(db.createDb());
  623. var attachmentData = "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=";
  624. db.bulkSave(makeDocs(20, 30, {
  625. _attachments:{
  626. "foo.txt": {
  627. content_type:"text/plain",
  628. data: attachmentData
  629. },
  630. "bar.txt": {
  631. content_type:"text/plain",
  632. data: attachmentData
  633. }
  634. }
  635. }));
  636. var mapFunction = function(doc) {
  637. var count = 0;
  638. for(var idx in doc._attachments) {
  639. count = count + 1;
  640. }
  641. emit(parseInt(doc._id), count);
  642. };
  643. var req = CouchDB.request("GET", "/" + db_name + "/_changes?include_docs=true");
  644. var resp = JSON.parse(req.responseText);
  645. T(resp.results.length == 10);
  646. T(resp.results[0].doc._attachments['foo.txt'].stub === true);
  647. T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
  648. T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
  649. T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
  650. T(resp.results[0].doc._attachments['bar.txt'].stub === true);
  651. T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
  652. T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
  653. T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
  654. var req = CouchDB.request("GET", "/" + db_name + "/_changes?include_docs=true&attachments=true");
  655. var resp = JSON.parse(req.responseText);
  656. T(resp.results.length == 10);
  657. T(resp.results[0].doc._attachments['foo.txt'].stub === undefined);
  658. T(resp.results[0].doc._attachments['foo.txt'].data === attachmentData);
  659. T(resp.results[0].doc._attachments['foo.txt'].encoding === undefined);
  660. T(resp.results[0].doc._attachments['foo.txt'].encoded_length === undefined);
  661. T(resp.results[0].doc._attachments['bar.txt'].stub === undefined);
  662. T(resp.results[0].doc._attachments['bar.txt'].data == attachmentData);
  663. T(resp.results[0].doc._attachments['bar.txt'].encoding === undefined);
  664. T(resp.results[0].doc._attachments['bar.txt'].encoded_length === undefined);
  665. var req = CouchDB.request("GET", "/" + db_name + "/_changes?include_docs=true&att_encoding_info=true");
  666. var resp = JSON.parse(req.responseText);
  667. T(resp.results.length == 10);
  668. T(resp.results[0].doc._attachments['foo.txt'].stub === true);
  669. T(resp.results[0].doc._attachments['foo.txt'].data === undefined);
  670. T(resp.results[0].doc._attachments['foo.txt'].encoding === "gzip");
  671. T(resp.results[0].doc._attachments['foo.txt'].encoded_length === 47);
  672. T(resp.results[0].doc._attachments['bar.txt'].stub === true);
  673. T(resp.results[0].doc._attachments['bar.txt'].data === undefined);
  674. T(resp.results[0].doc._attachments['bar.txt'].encoding === "gzip");
  675. T(resp.results[0].doc._attachments['bar.txt'].encoded_length === 47);
  676. db.deleteDb();
  677. };