SettingADocumentTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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. * DS207: Consider shorter variations of null checks
  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. chai.should()
  16. const { expect } = require('chai')
  17. const Settings = require('settings-sharelatex')
  18. const rclient_du = require('redis-sharelatex').createClient(
  19. Settings.redis.documentupdater
  20. )
  21. const Keys = Settings.redis.documentupdater.key_schema
  22. const MockTrackChangesApi = require('./helpers/MockTrackChangesApi')
  23. const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi')
  24. const MockWebApi = require('./helpers/MockWebApi')
  25. const DocUpdaterClient = require('./helpers/DocUpdaterClient')
  26. const DocUpdaterApp = require('./helpers/DocUpdaterApp')
  27. describe('Setting a document', function () {
  28. before(function (done) {
  29. this.lines = ['one', 'two', 'three']
  30. this.version = 42
  31. this.update = {
  32. doc: this.doc_id,
  33. op: [
  34. {
  35. i: 'one and a half\n',
  36. p: 4
  37. }
  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. DocUpdaterApp.ensureRunning(done)
  49. })
  50. after(function () {
  51. MockTrackChangesApi.flushDoc.restore()
  52. MockProjectHistoryApi.flushProject.restore()
  53. MockWebApi.setDocument.restore()
  54. })
  55. describe('when the updated doc exists in the doc updater', function () {
  56. before(function (done) {
  57. this.project_id = DocUpdaterClient.randomId()
  58. this.doc_id = DocUpdaterClient.randomId()
  59. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  60. lines: this.lines,
  61. version: this.version
  62. })
  63. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => {
  64. if (error != null) {
  65. throw error
  66. }
  67. DocUpdaterClient.sendUpdate(
  68. this.project_id,
  69. this.doc_id,
  70. this.update,
  71. (error) => {
  72. if (error != null) {
  73. throw error
  74. }
  75. setTimeout(() => {
  76. DocUpdaterClient.setDocLines(
  77. this.project_id,
  78. this.doc_id,
  79. this.newLines,
  80. this.source,
  81. this.user_id,
  82. false,
  83. (error, res, body) => {
  84. this.statusCode = res.statusCode
  85. done()
  86. }
  87. )
  88. }, 200)
  89. }
  90. )
  91. })
  92. })
  93. after(function () {
  94. MockTrackChangesApi.flushDoc.reset()
  95. MockProjectHistoryApi.flushProject.reset()
  96. MockWebApi.setDocument.reset()
  97. })
  98. it('should return a 204 status code', function () {
  99. this.statusCode.should.equal(204)
  100. })
  101. it('should send the updated doc lines and version to the web api', function () {
  102. MockWebApi.setDocument
  103. .calledWith(this.project_id, this.doc_id, this.newLines)
  104. .should.equal(true)
  105. })
  106. it('should update the lines in the doc updater', function (done) {
  107. DocUpdaterClient.getDoc(
  108. this.project_id,
  109. this.doc_id,
  110. (error, res, doc) => {
  111. doc.lines.should.deep.equal(this.newLines)
  112. done()
  113. }
  114. )
  115. })
  116. it('should bump the version in the doc updater', function (done) {
  117. DocUpdaterClient.getDoc(
  118. this.project_id,
  119. this.doc_id,
  120. (error, res, doc) => {
  121. doc.version.should.equal(this.version + 2)
  122. done()
  123. }
  124. )
  125. })
  126. it('should leave the document in redis', function (done) {
  127. rclient_du.get(Keys.docLines({ doc_id: this.doc_id }), (error, lines) => {
  128. if (error != null) {
  129. throw error
  130. }
  131. expect(JSON.parse(lines)).to.deep.equal(this.newLines)
  132. done()
  133. })
  134. })
  135. })
  136. describe('when the updated doc does not exist in the doc updater', function () {
  137. before(function (done) {
  138. this.project_id = DocUpdaterClient.randomId()
  139. this.doc_id = DocUpdaterClient.randomId()
  140. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  141. lines: this.lines,
  142. version: this.version
  143. })
  144. DocUpdaterClient.setDocLines(
  145. this.project_id,
  146. this.doc_id,
  147. this.newLines,
  148. this.source,
  149. this.user_id,
  150. false,
  151. (error, res, body) => {
  152. this.statusCode = res.statusCode
  153. setTimeout(done, 200)
  154. }
  155. )
  156. })
  157. after(function () {
  158. MockTrackChangesApi.flushDoc.reset()
  159. MockProjectHistoryApi.flushProject.reset()
  160. MockWebApi.setDocument.reset()
  161. })
  162. it('should return a 204 status code', function () {
  163. this.statusCode.should.equal(204)
  164. })
  165. it('should send the updated doc lines to the web api', function () {
  166. MockWebApi.setDocument
  167. .calledWith(this.project_id, this.doc_id, this.newLines)
  168. .should.equal(true)
  169. })
  170. it('should flush track changes', function () {
  171. MockTrackChangesApi.flushDoc.calledWith(this.doc_id).should.equal(true)
  172. })
  173. it('should flush project history', function () {
  174. MockProjectHistoryApi.flushProject
  175. .calledWith(this.project_id)
  176. .should.equal(true)
  177. })
  178. it('should remove the document from redis', function (done) {
  179. rclient_du.get(Keys.docLines({ doc_id: this.doc_id }), (error, lines) => {
  180. if (error != null) {
  181. throw error
  182. }
  183. expect(lines).to.not.exist
  184. done()
  185. })
  186. })
  187. })
  188. describe('when the updated doc is too large for the body parser', function () {
  189. before(function (done) {
  190. this.project_id = DocUpdaterClient.randomId()
  191. this.doc_id = DocUpdaterClient.randomId()
  192. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  193. lines: this.lines,
  194. version: this.version
  195. })
  196. this.newLines = []
  197. while (
  198. JSON.stringify(this.newLines).length <= Settings.maxJsonRequestSize
  199. ) {
  200. this.newLines.push('(a long line of text)'.repeat(10000))
  201. }
  202. DocUpdaterClient.setDocLines(
  203. this.project_id,
  204. this.doc_id,
  205. this.newLines,
  206. this.source,
  207. this.user_id,
  208. false,
  209. (error, res, body) => {
  210. this.statusCode = res.statusCode
  211. setTimeout(done, 200)
  212. }
  213. )
  214. })
  215. after(function () {
  216. MockTrackChangesApi.flushDoc.reset()
  217. MockProjectHistoryApi.flushProject.reset()
  218. MockWebApi.setDocument.reset()
  219. })
  220. it('should return a 413 status code', function () {
  221. this.statusCode.should.equal(413)
  222. })
  223. it('should not send the updated doc lines to the web api', function () {
  224. MockWebApi.setDocument.called.should.equal(false)
  225. })
  226. it('should not flush track changes', function () {
  227. MockTrackChangesApi.flushDoc.called.should.equal(false)
  228. })
  229. it('should not flush project history', function () {
  230. MockProjectHistoryApi.flushProject.called.should.equal(false)
  231. })
  232. })
  233. describe('when the updated doc is large but under the bodyParser and HTTPController size limit', function () {
  234. before(function (done) {
  235. this.project_id = DocUpdaterClient.randomId()
  236. this.doc_id = DocUpdaterClient.randomId()
  237. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  238. lines: this.lines,
  239. version: this.version
  240. })
  241. this.newLines = []
  242. while (JSON.stringify(this.newLines).length < 2 * 1024 * 1024) {
  243. // limit in HTTPController
  244. this.newLines.push('(a long line of text)'.repeat(10000))
  245. }
  246. this.newLines.pop() // remove the line which took it over the limit
  247. DocUpdaterClient.setDocLines(
  248. this.project_id,
  249. this.doc_id,
  250. this.newLines,
  251. this.source,
  252. this.user_id,
  253. false,
  254. (error, res, body) => {
  255. this.statusCode = res.statusCode
  256. setTimeout(done, 200)
  257. }
  258. )
  259. })
  260. after(function () {
  261. MockTrackChangesApi.flushDoc.reset()
  262. MockProjectHistoryApi.flushProject.reset()
  263. MockWebApi.setDocument.reset()
  264. })
  265. it('should return a 204 status code', function () {
  266. this.statusCode.should.equal(204)
  267. })
  268. it('should send the updated doc lines to the web api', function () {
  269. MockWebApi.setDocument
  270. .calledWith(this.project_id, this.doc_id, this.newLines)
  271. .should.equal(true)
  272. })
  273. })
  274. describe('with track changes', function () {
  275. before(function () {
  276. this.lines = ['one', 'one and a half', 'two', 'three']
  277. this.id_seed = '587357bd35e64f6157'
  278. this.update = {
  279. doc: this.doc_id,
  280. op: [
  281. {
  282. d: 'one and a half\n',
  283. p: 4
  284. }
  285. ],
  286. meta: {
  287. tc: this.id_seed,
  288. user_id: this.user_id
  289. },
  290. v: this.version
  291. }
  292. })
  293. describe('with the undo flag', function () {
  294. before(function (done) {
  295. this.project_id = DocUpdaterClient.randomId()
  296. this.doc_id = DocUpdaterClient.randomId()
  297. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  298. lines: this.lines,
  299. version: this.version
  300. })
  301. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => {
  302. if (error != null) {
  303. throw error
  304. }
  305. DocUpdaterClient.sendUpdate(
  306. this.project_id,
  307. this.doc_id,
  308. this.update,
  309. (error) => {
  310. if (error != null) {
  311. throw error
  312. }
  313. // Go back to old lines, with undo flag
  314. DocUpdaterClient.setDocLines(
  315. this.project_id,
  316. this.doc_id,
  317. this.lines,
  318. this.source,
  319. this.user_id,
  320. true,
  321. (error, res, body) => {
  322. this.statusCode = res.statusCode
  323. setTimeout(done, 200)
  324. }
  325. )
  326. }
  327. )
  328. })
  329. })
  330. after(function () {
  331. MockTrackChangesApi.flushDoc.reset()
  332. MockProjectHistoryApi.flushProject.reset()
  333. MockWebApi.setDocument.reset()
  334. })
  335. it('should undo the tracked changes', function (done) {
  336. DocUpdaterClient.getDoc(
  337. this.project_id,
  338. this.doc_id,
  339. (error, res, data) => {
  340. if (error != null) {
  341. throw error
  342. }
  343. const { ranges } = data
  344. expect(ranges.changes).to.be.undefined
  345. done()
  346. }
  347. )
  348. })
  349. })
  350. describe('without the undo flag', function () {
  351. before(function (done) {
  352. this.project_id = DocUpdaterClient.randomId()
  353. this.doc_id = DocUpdaterClient.randomId()
  354. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  355. lines: this.lines,
  356. version: this.version
  357. })
  358. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => {
  359. if (error != null) {
  360. throw error
  361. }
  362. DocUpdaterClient.sendUpdate(
  363. this.project_id,
  364. this.doc_id,
  365. this.update,
  366. (error) => {
  367. if (error != null) {
  368. throw error
  369. }
  370. // Go back to old lines, without undo flag
  371. DocUpdaterClient.setDocLines(
  372. this.project_id,
  373. this.doc_id,
  374. this.lines,
  375. this.source,
  376. this.user_id,
  377. false,
  378. (error, res, body) => {
  379. this.statusCode = res.statusCode
  380. setTimeout(done, 200)
  381. }
  382. )
  383. }
  384. )
  385. })
  386. })
  387. after(function () {
  388. MockTrackChangesApi.flushDoc.reset()
  389. MockProjectHistoryApi.flushProject.reset()
  390. MockWebApi.setDocument.reset()
  391. })
  392. it('should not undo the tracked changes', function (done) {
  393. DocUpdaterClient.getDoc(
  394. this.project_id,
  395. this.doc_id,
  396. (error, res, data) => {
  397. if (error != null) {
  398. throw error
  399. }
  400. const { ranges } = data
  401. expect(ranges.changes.length).to.equal(1)
  402. done()
  403. }
  404. )
  405. })
  406. })
  407. })
  408. })