SettingADocumentTests.js 13 KB

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