SettingADocumentTests.js 13 KB

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