SettingADocumentTests.js 11 KB

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