DiffManagerTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /* eslint-disable
  2. camelcase,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const sinon = require('sinon');
  14. const chai = require('chai');
  15. const should = chai.should();
  16. const { expect } = chai;
  17. const modulePath = "../../../../app/js/DiffManager.js";
  18. const SandboxedModule = require('sandboxed-module');
  19. describe("DiffManager", function() {
  20. beforeEach(function() {
  21. this.DiffManager = SandboxedModule.require(modulePath, { requires: {
  22. "logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }),
  23. "./UpdatesManager": (this.UpdatesManager = {}),
  24. "./DocumentUpdaterManager": (this.DocumentUpdaterManager = {}),
  25. "./DiffGenerator": (this.DiffGenerator = {})
  26. }
  27. });
  28. this.callback = sinon.stub();
  29. this.from = new Date();
  30. this.to = new Date(Date.now() + 10000);
  31. this.project_id = "mock-project-id";
  32. return this.doc_id = "mock-doc-id";
  33. });
  34. describe("getLatestDocAndUpdates", function() {
  35. beforeEach(function() {
  36. this.content = "hello world";
  37. this.version = 42;
  38. this.updates = [ "mock-update-1", "mock-update-2" ];
  39. this.DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(2, null, this.content, this.version);
  40. return this.UpdatesManager.getDocUpdatesWithUserInfo = sinon.stub().callsArgWith(3, null, this.updates);
  41. });
  42. describe("with a fromVersion", function() {
  43. beforeEach(function() {
  44. return this.DiffManager.getLatestDocAndUpdates(this.project_id, this.doc_id, this.from, this.callback);
  45. });
  46. it("should get the latest version of the doc", function() {
  47. return this.DocumentUpdaterManager.getDocument
  48. .calledWith(this.project_id, this.doc_id)
  49. .should.equal(true);
  50. });
  51. it("should get the latest updates", function() {
  52. return this.UpdatesManager.getDocUpdatesWithUserInfo
  53. .calledWith(this.project_id, this.doc_id, {from: this.from})
  54. .should.equal(true);
  55. });
  56. return it("should call the callback with the content, version and updates", function() {
  57. return this.callback.calledWith(null, this.content, this.version, this.updates).should.equal(true);
  58. });
  59. });
  60. return describe("with no fromVersion", function() {
  61. beforeEach(function() {
  62. return this.DiffManager.getLatestDocAndUpdates(this.project_id, this.doc_id, null, this.callback);
  63. });
  64. it("should get the latest version of the doc", function() {
  65. return this.DocumentUpdaterManager.getDocument
  66. .calledWith(this.project_id, this.doc_id)
  67. .should.equal(true);
  68. });
  69. it("should not get the latest updates", function() {
  70. return this.UpdatesManager.getDocUpdatesWithUserInfo
  71. .called.should.equal(false);
  72. });
  73. return it("should call the callback with the content, version and blank updates", function() {
  74. return this.callback.calledWith(null, this.content, this.version, []).should.equal(true);
  75. });
  76. });
  77. });
  78. describe("getDiff", function() {
  79. beforeEach(function() {
  80. this.content = "hello world";
  81. // Op versions are the version they were applied to, so doc is always one version
  82. // ahead.s
  83. this.version = 43;
  84. this.updates = [
  85. { op: "mock-4", v: 42, meta: { start_ts: new Date(this.to.getTime() + 20)} },
  86. { op: "mock-3", v: 41, meta: { start_ts: new Date(this.to.getTime() + 10)} },
  87. { op: "mock-2", v: 40, meta: { start_ts: new Date(this.to.getTime() - 10)} },
  88. { op: "mock-1", v: 39, meta: { start_ts: new Date(this.to.getTime() - 20)} }
  89. ];
  90. this.fromVersion = 39;
  91. this.toVersion = 40;
  92. this.diffed_updates = this.updates.slice(2);
  93. this.rewound_content = "rewound-content";
  94. return this.diff = [ {u: "mock-diff"} ];});
  95. describe("with matching versions", function() {
  96. beforeEach(function() {
  97. this.DiffManager.getDocumentBeforeVersion = sinon.stub().callsArgWith(3, null, this.rewound_content, this.updates);
  98. this.DiffGenerator.buildDiff = sinon.stub().returns(this.diff);
  99. return this.DiffManager.getDiff(this.project_id, this.doc_id, this.fromVersion, this.toVersion, this.callback);
  100. });
  101. it("should get the latest doc and version with all recent updates", function() {
  102. return this.DiffManager.getDocumentBeforeVersion
  103. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  104. .should.equal(true);
  105. });
  106. it("should generate the diff", function() {
  107. return this.DiffGenerator.buildDiff
  108. .calledWith(this.rewound_content, this.diffed_updates.slice().reverse())
  109. .should.equal(true);
  110. });
  111. return it("should call the callback with the diff", function() {
  112. return this.callback.calledWith(null, this.diff).should.equal(true);
  113. });
  114. });
  115. return describe("when the updates are inconsistent", function() {
  116. beforeEach(function() {
  117. this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
  118. this.DiffGenerator.buildDiff = sinon.stub().throws(this.error = new Error("inconsistent!"));
  119. return this.DiffManager.getDiff(this.project_id, this.doc_id, this.fromVersion, this.toVersion, this.callback);
  120. });
  121. return it("should call the callback with an error", function() {
  122. return this.callback
  123. .calledWith(this.error)
  124. .should.equal(true);
  125. });
  126. });
  127. });
  128. describe("getDocumentBeforeVersion", function() {
  129. beforeEach(function() {
  130. this.DiffManager._tryGetDocumentBeforeVersion = sinon.stub();
  131. this.document = "mock-documents";
  132. return this.rewound_updates = "mock-rewound-updates";
  133. });
  134. describe("succesfully", function() {
  135. beforeEach(function() {
  136. this.DiffManager._tryGetDocumentBeforeVersion.yields(null, this.document, this.rewound_updates);
  137. return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
  138. });
  139. it("should call _tryGetDocumentBeforeVersion", function() {
  140. return this.DiffManager._tryGetDocumentBeforeVersion
  141. .calledWith(this.project_id, this.doc_id, this.version)
  142. .should.equal(true);
  143. });
  144. return it("should call the callback with the response", function() {
  145. return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
  146. });
  147. });
  148. describe("with a retry needed", function() {
  149. beforeEach(function() {
  150. let retried = false;
  151. this.DiffManager._tryGetDocumentBeforeVersion = (project_id, doc_id, version, callback) => {
  152. if (!retried) {
  153. retried = true;
  154. const error = new Error();
  155. error.retry = true;
  156. return callback(error);
  157. } else {
  158. return callback(null, this.document, this.rewound_updates);
  159. }
  160. };
  161. sinon.spy(this.DiffManager, "_tryGetDocumentBeforeVersion");
  162. return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
  163. });
  164. it("should call _tryGetDocumentBeforeVersion twice", function() {
  165. return this.DiffManager._tryGetDocumentBeforeVersion
  166. .calledTwice
  167. .should.equal(true);
  168. });
  169. return it("should call the callback with the response", function() {
  170. return this.callback.calledWith(null, this.document, this.rewound_updates).should.equal(true);
  171. });
  172. });
  173. describe("with a non-retriable error", function() {
  174. beforeEach(function() {
  175. this.error = new Error("oops");
  176. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error);
  177. return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
  178. });
  179. it("should call _tryGetDocumentBeforeVersion once", function() {
  180. return this.DiffManager._tryGetDocumentBeforeVersion
  181. .calledOnce
  182. .should.equal(true);
  183. });
  184. return it("should call the callback with the error", function() {
  185. return this.callback.calledWith(this.error).should.equal(true);
  186. });
  187. });
  188. return describe("when retry limit is matched", function() {
  189. beforeEach(function() {
  190. this.error = new Error("oops");
  191. this.error.retry = true;
  192. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error);
  193. return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.version, this.callback);
  194. });
  195. it("should call _tryGetDocumentBeforeVersion three times (max retries)", function() {
  196. return this.DiffManager._tryGetDocumentBeforeVersion
  197. .calledThrice
  198. .should.equal(true);
  199. });
  200. return it("should call the callback with the error", function() {
  201. return this.callback.calledWith(this.error).should.equal(true);
  202. });
  203. });
  204. });
  205. return describe("_tryGetDocumentBeforeVersion", function() {
  206. beforeEach(function() {
  207. this.content = "hello world";
  208. // Op versions are the version they were applied to, so doc is always one version
  209. // ahead.s
  210. this.version = 43;
  211. this.updates = [
  212. { op: "mock-4", v: 42, meta: { start_ts: new Date(this.to.getTime() + 20)} },
  213. { op: "mock-3", v: 41, meta: { start_ts: new Date(this.to.getTime() + 10)} },
  214. { op: "mock-2", v: 40, meta: { start_ts: new Date(this.to.getTime() - 10)} },
  215. { op: "mock-1", v: 39, meta: { start_ts: new Date(this.to.getTime() - 20)} }
  216. ];
  217. this.fromVersion = 39;
  218. this.rewound_content = "rewound-content";
  219. return this.diff = [ {u: "mock-diff"} ];});
  220. describe("with matching versions", function() {
  221. beforeEach(function() {
  222. this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
  223. this.DiffGenerator.rewindUpdates = sinon.spy((content, updates) => {
  224. // the rewindUpdates method reverses the 'updates' array
  225. updates.reverse();
  226. return this.rewound_content;
  227. });
  228. this.rewindUpdatesWithArgs = this.DiffGenerator.rewindUpdates.withArgs(this.content, this.updates.slice().reverse());
  229. return this.DiffManager._tryGetDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
  230. });
  231. it("should get the latest doc and version with all recent updates", function() {
  232. return this.DiffManager.getLatestDocAndUpdates
  233. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  234. .should.equal(true);
  235. });
  236. it("should rewind the diff", function() {
  237. return sinon.assert.calledOnce(this.rewindUpdatesWithArgs);
  238. });
  239. return it("should call the callback with the rewound document and updates", function() {
  240. return this.callback.calledWith(null, this.rewound_content, this.updates).should.equal(true);
  241. });
  242. });
  243. describe("with mismatching versions", function() {
  244. beforeEach(function() {
  245. this.version = 50;
  246. this.updates = [ { op: "mock-1", v: 40 }, { op: "mock-1", v: 39 } ];
  247. this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
  248. return this.DiffManager._tryGetDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
  249. });
  250. return it("should call the callback with an error with retry = true set", function() {
  251. this.callback.calledOnce.should.equal(true);
  252. const error = this.callback.args[0][0];
  253. return expect(error.retry).to.equal(true);
  254. });
  255. });
  256. return describe("when the updates are inconsistent", function() {
  257. beforeEach(function() {
  258. this.DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(3, null, this.content, this.version, this.updates);
  259. this.DiffGenerator.rewindUpdates = sinon.stub().throws(this.error = new Error("inconsistent!"));
  260. return this.DiffManager.getDocumentBeforeVersion(this.project_id, this.doc_id, this.fromVersion, this.callback);
  261. });
  262. return it("should call the callback with an error", function() {
  263. return this.callback
  264. .calledWith(this.error)
  265. .should.equal(true);
  266. });
  267. });
  268. });
  269. });