RangesTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /* eslint-disable
  2. handle-callback-err,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const sinon = require("sinon");
  15. const chai = require("chai");
  16. chai.should();
  17. const {
  18. expect
  19. } = chai;
  20. const async = require("async");
  21. const {db, ObjectId} = require("../../../app/js/mongojs");
  22. const MockWebApi = require("./helpers/MockWebApi");
  23. const DocUpdaterClient = require("./helpers/DocUpdaterClient");
  24. const DocUpdaterApp = require("./helpers/DocUpdaterApp");
  25. describe("Ranges", function() {
  26. before(function(done) { return DocUpdaterApp.ensureRunning(done); });
  27. describe("tracking changes from ops", function() {
  28. before(function(done) {
  29. this.project_id = DocUpdaterClient.randomId();
  30. this.user_id = DocUpdaterClient.randomId();
  31. this.id_seed = "587357bd35e64f6157";
  32. this.doc = {
  33. id: DocUpdaterClient.randomId(),
  34. lines: ["aaa"]
  35. };
  36. this.updates = [{
  37. doc: this.doc.id,
  38. op: [{ i: "123", p: 1 }],
  39. v: 0,
  40. meta: { user_id: this.user_id }
  41. }, {
  42. doc: this.doc.id,
  43. op: [{ i: "456", p: 5 }],
  44. v: 1,
  45. meta: { user_id: this.user_id, tc: this.id_seed }
  46. }, {
  47. doc: this.doc.id,
  48. op: [{ d: "12", p: 1 }],
  49. v: 2,
  50. meta: { user_id: this.user_id }
  51. }];
  52. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  53. lines: this.doc.lines,
  54. version: 0
  55. });
  56. const jobs = [];
  57. for (const update of Array.from(this.updates)) {
  58. (update => {
  59. return jobs.push(callback => DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update, callback));
  60. })(update);
  61. }
  62. return DocUpdaterApp.ensureRunning(error => {
  63. if (error != null) { throw error; }
  64. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  65. if (error != null) { throw error; }
  66. return async.series(jobs, (error) => {
  67. if (error != null) { throw error; }
  68. return done();
  69. });
  70. });
  71. });
  72. });
  73. it("should update the ranges", function(done) {
  74. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  75. if (error != null) { throw error; }
  76. const {
  77. ranges
  78. } = data;
  79. const change = ranges.changes[0];
  80. change.op.should.deep.equal({ i: "456", p: 3 });
  81. change.id.should.equal(this.id_seed + "000001");
  82. change.metadata.user_id.should.equal(this.user_id);
  83. return done();
  84. });
  85. });
  86. return describe("Adding comments", function() {
  87. describe("standalone", function() {
  88. before(function(done) {
  89. this.project_id = DocUpdaterClient.randomId();
  90. this.user_id = DocUpdaterClient.randomId();
  91. this.doc = {
  92. id: DocUpdaterClient.randomId(),
  93. lines: ["foo bar baz"]
  94. };
  95. this.updates = [{
  96. doc: this.doc.id,
  97. op: [{ c: "bar", p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  98. v: 0
  99. }];
  100. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  101. lines: this.doc.lines,
  102. version: 0
  103. });
  104. const jobs = [];
  105. for (const update of Array.from(this.updates)) {
  106. (update => {
  107. return jobs.push(callback => DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update, callback));
  108. })(update);
  109. }
  110. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  111. if (error != null) { throw error; }
  112. return async.series(jobs, (error) => {
  113. if (error != null) { throw error; }
  114. return setTimeout(done, 200);
  115. });
  116. });
  117. });
  118. return it("should update the ranges", function(done) {
  119. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  120. if (error != null) { throw error; }
  121. const {
  122. ranges
  123. } = data;
  124. const comment = ranges.comments[0];
  125. comment.op.should.deep.equal({ c: "bar", p: 4, t: this.tid });
  126. comment.id.should.equal(this.tid);
  127. return done();
  128. });
  129. });
  130. });
  131. return describe("with conflicting ops needing OT", function() {
  132. before(function(done) {
  133. this.project_id = DocUpdaterClient.randomId();
  134. this.user_id = DocUpdaterClient.randomId();
  135. this.doc = {
  136. id: DocUpdaterClient.randomId(),
  137. lines: ["foo bar baz"]
  138. };
  139. this.updates = [{
  140. doc: this.doc.id,
  141. op: [{ i: "ABC", p: 3 }],
  142. v: 0,
  143. meta: { user_id: this.user_id }
  144. }, {
  145. doc: this.doc.id,
  146. op: [{ c: "bar", p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  147. v: 0
  148. }];
  149. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  150. lines: this.doc.lines,
  151. version: 0
  152. });
  153. const jobs = [];
  154. for (const update of Array.from(this.updates)) {
  155. (update => {
  156. return jobs.push(callback => DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update, callback));
  157. })(update);
  158. }
  159. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  160. if (error != null) { throw error; }
  161. return async.series(jobs, (error) => {
  162. if (error != null) { throw error; }
  163. return setTimeout(done, 200);
  164. });
  165. });
  166. });
  167. return it("should update the comments with the OT shifted comment", function(done) {
  168. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  169. if (error != null) { throw error; }
  170. const {
  171. ranges
  172. } = data;
  173. const comment = ranges.comments[0];
  174. comment.op.should.deep.equal({ c: "bar", p: 7, t: this.tid });
  175. return done();
  176. });
  177. });
  178. });
  179. });
  180. });
  181. describe("Loading ranges from persistence layer", function() {
  182. before(function(done) {
  183. this.project_id = DocUpdaterClient.randomId();
  184. this.user_id = DocUpdaterClient.randomId();
  185. this.id_seed = "587357bd35e64f6157";
  186. this.doc = {
  187. id: DocUpdaterClient.randomId(),
  188. lines: ["a123aa"]
  189. };
  190. this.update = {
  191. doc: this.doc.id,
  192. op: [{ i: "456", p: 5 }],
  193. v: 0,
  194. meta: { user_id: this.user_id, tc: this.id_seed }
  195. };
  196. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  197. lines: this.doc.lines,
  198. version: 0,
  199. ranges: {
  200. changes: [{
  201. op: { i: "123", p: 1 },
  202. metadata: {
  203. user_id: this.user_id,
  204. ts: new Date()
  205. }
  206. }]
  207. }
  208. });
  209. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  210. if (error != null) { throw error; }
  211. return DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, this.update, (error) => {
  212. if (error != null) { throw error; }
  213. return setTimeout(done, 200);
  214. });
  215. });
  216. });
  217. it("should have preloaded the existing ranges", function(done) {
  218. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  219. if (error != null) { throw error; }
  220. const {changes} = data.ranges;
  221. changes[0].op.should.deep.equal({ i: "123", p: 1 });
  222. changes[1].op.should.deep.equal({ i: "456", p: 5 });
  223. return done();
  224. });
  225. });
  226. return it("should flush the ranges to the persistence layer again", function(done) {
  227. return DocUpdaterClient.flushDoc(this.project_id, this.doc.id, error => {
  228. if (error != null) { throw error; }
  229. return MockWebApi.getDocument(this.project_id, this.doc.id, (error, doc) => {
  230. const {changes} = doc.ranges;
  231. changes[0].op.should.deep.equal({ i: "123", p: 1 });
  232. changes[1].op.should.deep.equal({ i: "456", p: 5 });
  233. return done();
  234. });
  235. });
  236. });
  237. });
  238. describe("accepting a change", function() {
  239. before(function(done) {
  240. this.project_id = DocUpdaterClient.randomId();
  241. this.user_id = DocUpdaterClient.randomId();
  242. this.id_seed = "587357bd35e64f6157";
  243. this.doc = {
  244. id: DocUpdaterClient.randomId(),
  245. lines: ["aaa"]
  246. };
  247. this.update = {
  248. doc: this.doc.id,
  249. op: [{ i: "456", p: 1 }],
  250. v: 0,
  251. meta: { user_id: this.user_id, tc: this.id_seed }
  252. };
  253. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  254. lines: this.doc.lines,
  255. version: 0
  256. });
  257. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  258. if (error != null) { throw error; }
  259. return DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, this.update, error => {
  260. if (error != null) { throw error; }
  261. return setTimeout(() => {
  262. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  263. if (error != null) { throw error; }
  264. const {
  265. ranges
  266. } = data;
  267. const change = ranges.changes[0];
  268. change.op.should.deep.equal({ i: "456", p: 1 });
  269. change.id.should.equal(this.id_seed + "000001");
  270. change.metadata.user_id.should.equal(this.user_id);
  271. return done();
  272. });
  273. }
  274. , 200);
  275. });
  276. });
  277. });
  278. return it("should remove the change after accepting", function(done) {
  279. return DocUpdaterClient.acceptChange(this.project_id, this.doc.id, this.id_seed + "000001", error => {
  280. if (error != null) { throw error; }
  281. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  282. if (error != null) { throw error; }
  283. expect(data.ranges.changes).to.be.undefined;
  284. return done();
  285. });
  286. });
  287. });
  288. });
  289. describe("deleting a comment range", function() {
  290. before(function(done) {
  291. this.project_id = DocUpdaterClient.randomId();
  292. this.user_id = DocUpdaterClient.randomId();
  293. this.doc = {
  294. id: DocUpdaterClient.randomId(),
  295. lines: ["foo bar"]
  296. };
  297. this.update = {
  298. doc: this.doc.id,
  299. op: [{ c: "bar", p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  300. v: 0
  301. };
  302. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  303. lines: this.doc.lines,
  304. version: 0
  305. });
  306. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  307. if (error != null) { throw error; }
  308. return DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, this.update, error => {
  309. if (error != null) { throw error; }
  310. return setTimeout(() => {
  311. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  312. if (error != null) { throw error; }
  313. const {
  314. ranges
  315. } = data;
  316. const change = ranges.comments[0];
  317. change.op.should.deep.equal({ c: "bar", p: 4, t: this.tid });
  318. change.id.should.equal(this.tid);
  319. return done();
  320. });
  321. }
  322. , 200);
  323. });
  324. });
  325. });
  326. return it("should remove the comment range", function(done) {
  327. return DocUpdaterClient.removeComment(this.project_id, this.doc.id, this.tid, (error, res) => {
  328. if (error != null) { throw error; }
  329. expect(res.statusCode).to.equal(204);
  330. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  331. if (error != null) { throw error; }
  332. expect(data.ranges.comments).to.be.undefined;
  333. return done();
  334. });
  335. });
  336. });
  337. });
  338. describe("tripping range size limit", function() {
  339. before(function(done) {
  340. this.project_id = DocUpdaterClient.randomId();
  341. this.user_id = DocUpdaterClient.randomId();
  342. this.id_seed = DocUpdaterClient.randomId();
  343. this.doc = {
  344. id: DocUpdaterClient.randomId(),
  345. lines: ["aaa"]
  346. };
  347. this.i = new Array(3 * 1024 * 1024).join("a");
  348. this.updates = [{
  349. doc: this.doc.id,
  350. op: [{ i: this.i, p: 1 }],
  351. v: 0,
  352. meta: { user_id: this.user_id, tc: this.id_seed }
  353. }];
  354. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  355. lines: this.doc.lines,
  356. version: 0
  357. });
  358. const jobs = [];
  359. for (const update of Array.from(this.updates)) {
  360. (update => {
  361. return jobs.push(callback => DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update, callback));
  362. })(update);
  363. }
  364. return DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  365. if (error != null) { throw error; }
  366. return async.series(jobs, (error) => {
  367. if (error != null) { throw error; }
  368. return setTimeout(done, 200);
  369. });
  370. });
  371. });
  372. return it("should not update the ranges", function(done) {
  373. return DocUpdaterClient.getDoc(this.project_id, this.doc.id, (error, res, data) => {
  374. if (error != null) { throw error; }
  375. const {
  376. ranges
  377. } = data;
  378. expect(ranges.changes).to.be.undefined;
  379. return done();
  380. });
  381. });
  382. });
  383. return describe("deleting text surrounding a comment", function() {
  384. before(function(done) {
  385. this.project_id = DocUpdaterClient.randomId();
  386. this.user_id = DocUpdaterClient.randomId();
  387. this.doc_id = DocUpdaterClient.randomId();
  388. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  389. lines: ["foo bar baz"],
  390. version: 0,
  391. ranges: {
  392. comments: [{
  393. op: { c: "a", p: 5, tid: (this.tid = DocUpdaterClient.randomId()) },
  394. metadata: {
  395. user_id: this.user_id,
  396. ts: new Date()
  397. }
  398. }]
  399. }
  400. });
  401. this.updates = [{
  402. doc: this.doc_id,
  403. op: [{ d: "foo ", p: 0 }],
  404. v: 0,
  405. meta: { user_id: this.user_id }
  406. }, {
  407. doc: this.doc_id,
  408. op: [{ d: "bar ", p: 0 }],
  409. v: 1,
  410. meta: { user_id: this.user_id }
  411. }];
  412. const jobs = [];
  413. for (const update of Array.from(this.updates)) {
  414. (update => {
  415. return jobs.push(callback => DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, update, callback));
  416. })(update);
  417. }
  418. return DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  419. if (error != null) { throw error; }
  420. return async.series(jobs, function(error) {
  421. if (error != null) { throw error; }
  422. return setTimeout(() => {
  423. return DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, data) => {
  424. if (error != null) { throw error; }
  425. return done();
  426. });
  427. }
  428. , 200);
  429. });
  430. });
  431. });
  432. return it("should write a snapshot from before the destructive change", function(done) {
  433. return DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, data) => {
  434. if (error != null) { return done(error); }
  435. return db.docSnapshots.find({
  436. project_id: ObjectId(this.project_id),
  437. doc_id: ObjectId(this.doc_id)
  438. }, (error, docSnapshots) => {
  439. if (error != null) { return done(error); }
  440. expect(docSnapshots.length).to.equal(1);
  441. expect(docSnapshots[0].version).to.equal(1);
  442. expect(docSnapshots[0].lines).to.deep.equal(["bar baz"]);
  443. expect(docSnapshots[0].ranges.comments[0].op).to.deep.equal({
  444. c: "a",
  445. p: 1,
  446. tid: this.tid
  447. });
  448. return done();
  449. });
  450. });
  451. });
  452. });
  453. });