SettingADocumentTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const sinon = require("sinon");
  16. const chai = require("chai");
  17. chai.should();
  18. const {
  19. expect
  20. } = require("chai");
  21. const Settings = require('settings-sharelatex');
  22. const rclient_du = require("redis-sharelatex").createClient(Settings.redis.documentupdater);
  23. const Keys = Settings.redis.documentupdater.key_schema;
  24. const MockTrackChangesApi = require("./helpers/MockTrackChangesApi");
  25. const MockProjectHistoryApi = require("./helpers/MockProjectHistoryApi");
  26. const MockWebApi = require("./helpers/MockWebApi");
  27. const DocUpdaterClient = require("./helpers/DocUpdaterClient");
  28. const DocUpdaterApp = require("./helpers/DocUpdaterApp");
  29. describe("Setting a document", function() {
  30. before(function(done) {
  31. this.lines = ["one", "two", "three"];
  32. this.version = 42;
  33. this.update = {
  34. doc: this.doc_id,
  35. op: [{
  36. i: "one and a half\n",
  37. p: 4
  38. }],
  39. v: this.version
  40. };
  41. this.result = ["one", "one and a half", "two", "three"];
  42. this.newLines = ["these", "are", "the", "new", "lines"];
  43. this.source = "dropbox";
  44. this.user_id = "user-id-123";
  45. sinon.spy(MockTrackChangesApi, "flushDoc");
  46. sinon.spy(MockProjectHistoryApi, "flushProject");
  47. sinon.spy(MockWebApi, "setDocument");
  48. return DocUpdaterApp.ensureRunning(done);
  49. });
  50. after(function() {
  51. MockTrackChangesApi.flushDoc.restore();
  52. MockProjectHistoryApi.flushProject.restore();
  53. return MockWebApi.setDocument.restore();
  54. });
  55. describe("when the updated doc exists in the doc updater", function() {
  56. before(function(done) {
  57. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  58. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  59. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  60. if (error != null) { throw error; }
  61. return DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, this.update, error => {
  62. if (error != null) { throw error; }
  63. return setTimeout(() => {
  64. return DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => {
  65. this.statusCode = res.statusCode;
  66. return done();
  67. });
  68. }
  69. , 200);
  70. });
  71. });
  72. return null;
  73. });
  74. after(function() {
  75. MockTrackChangesApi.flushDoc.reset();
  76. MockProjectHistoryApi.flushProject.reset();
  77. return MockWebApi.setDocument.reset();
  78. });
  79. it("should return a 204 status code", function() {
  80. return this.statusCode.should.equal(204);
  81. });
  82. it("should send the updated doc lines and version to the web api", function() {
  83. return MockWebApi.setDocument
  84. .calledWith(this.project_id, this.doc_id, this.newLines)
  85. .should.equal(true);
  86. });
  87. it("should update the lines in the doc updater", function(done) {
  88. DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, doc) => {
  89. doc.lines.should.deep.equal(this.newLines);
  90. return done();
  91. });
  92. return null;
  93. });
  94. it("should bump the version in the doc updater", function(done) {
  95. DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, doc) => {
  96. doc.version.should.equal(this.version + 2);
  97. return done();
  98. });
  99. return null;
  100. });
  101. return it("should leave the document in redis", function(done) {
  102. rclient_du.get(Keys.docLines({doc_id: this.doc_id}), (error, lines) => {
  103. if (error != null) { throw error; }
  104. expect(JSON.parse(lines)).to.deep.equal(this.newLines);
  105. return done();
  106. });
  107. return null;
  108. });
  109. });
  110. describe("when the updated doc does not exist in the doc updater", function() {
  111. before(function(done) {
  112. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  113. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  114. DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => {
  115. this.statusCode = res.statusCode;
  116. return setTimeout(done, 200);
  117. });
  118. return null;
  119. });
  120. after(function() {
  121. MockTrackChangesApi.flushDoc.reset();
  122. MockProjectHistoryApi.flushProject.reset();
  123. return MockWebApi.setDocument.reset();
  124. });
  125. it("should return a 204 status code", function() {
  126. return this.statusCode.should.equal(204);
  127. });
  128. it("should send the updated doc lines to the web api", function() {
  129. return MockWebApi.setDocument
  130. .calledWith(this.project_id, this.doc_id, this.newLines)
  131. .should.equal(true);
  132. });
  133. it("should flush track changes", function() {
  134. return MockTrackChangesApi.flushDoc.calledWith(this.doc_id).should.equal(true);
  135. });
  136. it("should flush project history", function() {
  137. return MockProjectHistoryApi.flushProject.calledWith(this.project_id).should.equal(true);
  138. });
  139. return it("should remove the document from redis", function(done) {
  140. rclient_du.get(Keys.docLines({doc_id: this.doc_id}), (error, lines) => {
  141. if (error != null) { throw error; }
  142. expect(lines).to.not.exist;
  143. return done();
  144. });
  145. return null;
  146. });
  147. });
  148. describe("when the updated doc is too large for the body parser", function() {
  149. before(function(done) {
  150. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  151. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  152. this.newLines = [];
  153. while (JSON.stringify(this.newLines).length < (Settings.max_doc_length + (64 * 1024))) {
  154. this.newLines.push("(a long line of text)".repeat(10000));
  155. }
  156. DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => {
  157. this.statusCode = res.statusCode;
  158. return setTimeout(done, 200);
  159. });
  160. return null;
  161. });
  162. after(function() {
  163. MockTrackChangesApi.flushDoc.reset();
  164. MockProjectHistoryApi.flushProject.reset();
  165. return MockWebApi.setDocument.reset();
  166. });
  167. it("should return a 413 status code", function() {
  168. return this.statusCode.should.equal(413);
  169. });
  170. it("should not send the updated doc lines to the web api", function() { return MockWebApi.setDocument.called.should.equal(false); });
  171. it("should not flush track changes", function() { return MockTrackChangesApi.flushDoc.called.should.equal(false); });
  172. return it("should not flush project history", function() { return MockProjectHistoryApi.flushProject.called.should.equal(false); });
  173. });
  174. describe("when the updated doc is large but under the bodyParser and HTTPController size limit", function() {
  175. before(function(done) {
  176. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  177. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  178. this.newLines = [];
  179. while (JSON.stringify(this.newLines).length < (2 * 1024 * 1024)) { // limit in HTTPController
  180. this.newLines.push("(a long line of text)".repeat(10000));
  181. }
  182. this.newLines.pop(); // remove the line which took it over the limit
  183. DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => {
  184. this.statusCode = res.statusCode;
  185. return setTimeout(done, 200);
  186. });
  187. return null;
  188. });
  189. after(function() {
  190. MockTrackChangesApi.flushDoc.reset();
  191. MockProjectHistoryApi.flushProject.reset();
  192. return MockWebApi.setDocument.reset();
  193. });
  194. it("should return a 204 status code", function() {
  195. return this.statusCode.should.equal(204);
  196. });
  197. return it("should send the updated doc lines to the web api", function() {
  198. return MockWebApi.setDocument
  199. .calledWith(this.project_id, this.doc_id, this.newLines)
  200. .should.equal(true);
  201. });
  202. });
  203. return describe("with track changes", function() {
  204. before(function() {
  205. this.lines = ["one", "one and a half", "two", "three"];
  206. this.id_seed = "587357bd35e64f6157";
  207. return this.update = {
  208. doc: this.doc_id,
  209. op: [{
  210. d: "one and a half\n",
  211. p: 4
  212. }],
  213. meta: {
  214. tc: this.id_seed,
  215. user_id: this.user_id
  216. },
  217. v: this.version
  218. };
  219. });
  220. describe("with the undo flag", function() {
  221. before(function(done) {
  222. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  223. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  224. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  225. if (error != null) { throw error; }
  226. return DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, this.update, error => {
  227. if (error != null) { throw error; }
  228. // Go back to old lines, with undo flag
  229. return DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.lines, this.source, this.user_id, true, (error, res, body) => {
  230. this.statusCode = res.statusCode;
  231. return setTimeout(done, 200);
  232. });
  233. });
  234. });
  235. return null;
  236. });
  237. after(function() {
  238. MockTrackChangesApi.flushDoc.reset();
  239. MockProjectHistoryApi.flushProject.reset();
  240. return MockWebApi.setDocument.reset();
  241. });
  242. return it("should undo the tracked changes", function(done) {
  243. DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, data) => {
  244. if (error != null) { throw error; }
  245. const {
  246. ranges
  247. } = data;
  248. expect(ranges.changes).to.be.undefined;
  249. return done();
  250. });
  251. return null;
  252. });
  253. });
  254. return describe("without the undo flag", function() {
  255. before(function(done) {
  256. [this.project_id, this.doc_id] = Array.from([DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]);
  257. MockWebApi.insertDoc(this.project_id, this.doc_id, {lines: this.lines, version: this.version});
  258. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  259. if (error != null) { throw error; }
  260. return DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, this.update, error => {
  261. if (error != null) { throw error; }
  262. // Go back to old lines, without undo flag
  263. return DocUpdaterClient.setDocLines(this.project_id, this.doc_id, this.lines, this.source, this.user_id, false, (error, res, body) => {
  264. this.statusCode = res.statusCode;
  265. return setTimeout(done, 200);
  266. });
  267. });
  268. });
  269. return null;
  270. });
  271. after(function() {
  272. MockTrackChangesApi.flushDoc.reset();
  273. MockProjectHistoryApi.flushProject.reset();
  274. return MockWebApi.setDocument.reset();
  275. });
  276. return it("should not undo the tracked changes", function(done) {
  277. DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res, data) => {
  278. if (error != null) { throw error; }
  279. const {
  280. ranges
  281. } = data;
  282. expect(ranges.changes.length).to.equal(1);
  283. return done();
  284. });
  285. return null;
  286. });
  287. });
  288. });
  289. });