SettingADocumentTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 <= Settings.maxJsonRequestSize
  215. ) {
  216. this.newLines.push('(a long line of text)'.repeat(10000))
  217. }
  218. DocUpdaterClient.setDocLines(
  219. this.project_id,
  220. this.doc_id,
  221. this.newLines,
  222. this.source,
  223. this.user_id,
  224. false,
  225. (error, res, body) => {
  226. this.statusCode = res.statusCode
  227. return setTimeout(done, 200)
  228. }
  229. )
  230. return null
  231. })
  232. after(function () {
  233. MockTrackChangesApi.flushDoc.reset()
  234. MockProjectHistoryApi.flushProject.reset()
  235. return MockWebApi.setDocument.reset()
  236. })
  237. it('should return a 413 status code', function () {
  238. return this.statusCode.should.equal(413)
  239. })
  240. it('should not send the updated doc lines to the web api', function () {
  241. return MockWebApi.setDocument.called.should.equal(false)
  242. })
  243. it('should not flush track changes', function () {
  244. return MockTrackChangesApi.flushDoc.called.should.equal(false)
  245. })
  246. return it('should not flush project history', function () {
  247. return MockProjectHistoryApi.flushProject.called.should.equal(false)
  248. })
  249. })
  250. describe('when the updated doc is large but under the bodyParser and HTTPController size limit', function () {
  251. before(function (done) {
  252. ;[this.project_id, this.doc_id] = Array.from([
  253. DocUpdaterClient.randomId(),
  254. DocUpdaterClient.randomId()
  255. ])
  256. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  257. lines: this.lines,
  258. version: this.version
  259. })
  260. this.newLines = []
  261. while (JSON.stringify(this.newLines).length < 2 * 1024 * 1024) {
  262. // limit in HTTPController
  263. this.newLines.push('(a long line of text)'.repeat(10000))
  264. }
  265. this.newLines.pop() // remove the line which took it over the limit
  266. DocUpdaterClient.setDocLines(
  267. this.project_id,
  268. this.doc_id,
  269. this.newLines,
  270. this.source,
  271. this.user_id,
  272. false,
  273. (error, res, body) => {
  274. this.statusCode = res.statusCode
  275. return setTimeout(done, 200)
  276. }
  277. )
  278. return null
  279. })
  280. after(function () {
  281. MockTrackChangesApi.flushDoc.reset()
  282. MockProjectHistoryApi.flushProject.reset()
  283. return MockWebApi.setDocument.reset()
  284. })
  285. it('should return a 204 status code', function () {
  286. return this.statusCode.should.equal(204)
  287. })
  288. return it('should send the updated doc lines to the web api', function () {
  289. return MockWebApi.setDocument
  290. .calledWith(this.project_id, this.doc_id, this.newLines)
  291. .should.equal(true)
  292. })
  293. })
  294. return describe('with track changes', function () {
  295. before(function () {
  296. this.lines = ['one', 'one and a half', 'two', 'three']
  297. this.id_seed = '587357bd35e64f6157'
  298. return (this.update = {
  299. doc: this.doc_id,
  300. op: [
  301. {
  302. d: 'one and a half\n',
  303. p: 4
  304. }
  305. ],
  306. meta: {
  307. tc: this.id_seed,
  308. user_id: this.user_id
  309. },
  310. v: this.version
  311. })
  312. })
  313. describe('with the undo flag', function () {
  314. before(function (done) {
  315. ;[this.project_id, this.doc_id] = Array.from([
  316. DocUpdaterClient.randomId(),
  317. DocUpdaterClient.randomId()
  318. ])
  319. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  320. lines: this.lines,
  321. version: this.version
  322. })
  323. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => {
  324. if (error != null) {
  325. throw error
  326. }
  327. return DocUpdaterClient.sendUpdate(
  328. this.project_id,
  329. this.doc_id,
  330. this.update,
  331. (error) => {
  332. if (error != null) {
  333. throw error
  334. }
  335. // Go back to old lines, with undo flag
  336. return DocUpdaterClient.setDocLines(
  337. this.project_id,
  338. this.doc_id,
  339. this.lines,
  340. this.source,
  341. this.user_id,
  342. true,
  343. (error, res, body) => {
  344. this.statusCode = res.statusCode
  345. return setTimeout(done, 200)
  346. }
  347. )
  348. }
  349. )
  350. })
  351. return null
  352. })
  353. after(function () {
  354. MockTrackChangesApi.flushDoc.reset()
  355. MockProjectHistoryApi.flushProject.reset()
  356. return MockWebApi.setDocument.reset()
  357. })
  358. return it('should undo the tracked changes', function (done) {
  359. DocUpdaterClient.getDoc(
  360. this.project_id,
  361. this.doc_id,
  362. (error, res, data) => {
  363. if (error != null) {
  364. throw error
  365. }
  366. const { ranges } = data
  367. expect(ranges.changes).to.be.undefined
  368. return done()
  369. }
  370. )
  371. return null
  372. })
  373. })
  374. return describe('without the undo flag', function () {
  375. before(function (done) {
  376. ;[this.project_id, this.doc_id] = Array.from([
  377. DocUpdaterClient.randomId(),
  378. DocUpdaterClient.randomId()
  379. ])
  380. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  381. lines: this.lines,
  382. version: this.version
  383. })
  384. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => {
  385. if (error != null) {
  386. throw error
  387. }
  388. return DocUpdaterClient.sendUpdate(
  389. this.project_id,
  390. this.doc_id,
  391. this.update,
  392. (error) => {
  393. if (error != null) {
  394. throw error
  395. }
  396. // Go back to old lines, without undo flag
  397. return DocUpdaterClient.setDocLines(
  398. this.project_id,
  399. this.doc_id,
  400. this.lines,
  401. this.source,
  402. this.user_id,
  403. false,
  404. (error, res, body) => {
  405. this.statusCode = res.statusCode
  406. return setTimeout(done, 200)
  407. }
  408. )
  409. }
  410. )
  411. })
  412. return null
  413. })
  414. after(function () {
  415. MockTrackChangesApi.flushDoc.reset()
  416. MockProjectHistoryApi.flushProject.reset()
  417. return MockWebApi.setDocument.reset()
  418. })
  419. return it('should not undo the tracked changes', function (done) {
  420. DocUpdaterClient.getDoc(
  421. this.project_id,
  422. this.doc_id,
  423. (error, res, data) => {
  424. if (error != null) {
  425. throw error
  426. }
  427. const { ranges } = data
  428. expect(ranges.changes.length).to.equal(1)
  429. return done()
  430. }
  431. )
  432. return null
  433. })
  434. })
  435. })
  436. })